SlideShare a Scribd company logo
Learned lessons in a
real world project
Luis Rovirosa
@luisrovirosa
www.codium.team
About me
+10 years doing web
applications
Team transformations
Lean & Craftsmanship
Agenda
Context
How to deal with a new project
Automate everything
Domain at heart
Ensure quality
Context
2 + 1 month project
2 part time developers
Green field project
Turn system
Context
How to deal with a
new project
There is no trust
We don’t know each other
Lack of business knowledge
Problems at beginning
Difficult to estimate
10-100 pages
1+ requirement documents
All must be done
What we usually receive
Everything is important
User stories
Split into Sprints
Vertical slices
Our recipe: the Sprint 0
Prioritize / Reduce scope
Iterative
2nd - Support final hardware
1st - Minimal functionality
1 CCU, 1 dispenser & 1 display.
No services | No printing real tickets
Neither users nor roles
3rd - Services + basic actions
How we broke it
4th - Roles & permissions
Learned lessons
Sprint 0 is the key
Simplify the problem
Prioritize
Deal with expectations
How we develop
Ensure quality
Automate everything
Domain at heart
How we develop
Automate everything
Automate infrastructure
Public docker images
Our own docker images
Docker-compose
Own docker image
// Dockerfile
FROM php:7.1-apache
# Dependencies
RUN docker-php-ext-install mysqli pdo pdo_mysql
RUN a2enmod rewrite
RUN curl -sS https://getcomposer.org/installer | php && 
mv composer.phar /usr/local/bin/composer
# Configuration
COPY apache.conf
/etc/apache2/sites-available/001-our-project.conf
RUN ln -s /etc/apache2/sites-available/001-our-project.conf
/etc/apache2/sites-enabled/
# Git and unzip to use composer
RUN apt-get update && apt-get install git unzip -y
Docker-compose
version: '3.3'
services:
webserver:
container_name: our_project_webserver
image: our-project-apache
build:
context: .
dockerfile: docker/apache/Dockerfile
volumes:
- .:/var/www/html
- ./vendor:/var/www/html/vendor:delegated
Automate test execution
// bitbucket-pipelines.yml
image: composer:latest
pipelines:
branches:
master:
- step:
caches:
- composer
script:
- composer install
- composer tests:unit
definitions:
caches:
composer: vendor
Automate commands
// Composer.json
"scripts": {
"server:code:update":[
"git pull",
"docker exec own-project_webserver
composer local:dependencies:update"
],
"local:dependencies:update":[
"composer install",
"composer local:database:update",
"composer local:cache:clear"
],
Learned lessons
Easy setup/update
Documentation
Double check on tests
Domain at heart
Ubiquitous language
Use Business names
Only 1 word for concept
Ubiquitous language
DDD
Framework
Application
Domain
Infrastructure
Framework Example
/**
* @Route("/callNextCustomer", name="call_next_customer")
*/
public function callNextCustomerAction(Request $request)
{
$deviceId = $request->get('deviceId');
/** @var CallNextCustomer $action */
$action = $this->container->get(CallNextCustomer::class);
$response = $action->execute(
new CallNextCustomerRequest($deviceId)
);
return new JsonResponse($response->turn());
}
Framework Example
/** @Route("/call_next_customer", name="call_next_customer")*/
public function callNextCustomerAction(Request $request)
{
return new JsonResponse(
$this->execute('call_next_customer_action', $request)
);
}
protected function execute(string $name, Request $request)
{
/** @var ActionSequence $action */
$action = $this->get($name);
return $action->execute($request);
}
Application Example
public function execute(Request $request): Response
{
/** @var CallNextCustomerRequest $request */
$device = $this->findDevice($request);
$turn = $this->findNextTurn($device);
$user = $this->findUser($request);
$this->markTurnAsCalled($turn, $device, $user);
$this->showOnDisplays($turn);
$this->notifyPendingCustomerStats();
return new CallNextCustomerResponse(
$turn->id(), $turn->value(), $turn->service()->id()
);
}
Domain Example
namespace MyProjectDomainModelUser;
interface UserRepository
{
public function save(User $user): void;
public function findById(string $id): User;
public function all(): Users;
}
First class collection
class Users extends ArrayCollection
{
public function canTalkTo(User $theUser): Users
{
// Code
}
public function orderByFullName(): Users
{
// Code
}
}
Decoupling Libraries
and Infrastructure
Domain Interface
Infrastructure implementation
// Message.Message.orm.yml
MyProjectDomainMessageMessage:
type: entity
table: message
id:
id:
type: string
fields:
fromUserId:
type: string
toUserId:
type: string
message:
type: string
Database mapping
Learned lessons
Simplifies the communication
Really decoupled
Increases complexity
Ensure quality
TDD Development
Different levels of testing
Test doubles manually
Acceptance tests
/** @test */
public function its_possible_to_access_the_counter
_call_unit_when_you_are_log_in()
{
$this->loginAsEmployee();
$this->makeGetRequest('/counter_call_unit');
$this->assertResponseIsSuccessful();
}
Framework tests
/** @test */
public function the_first_value_generated_is_1()
{
$generator = $this->aGenerator();
$nextValue = $generator->next();
$this->assertSame(1, $nextValue);
}
Unit tests
public function retrieve_a_persisted_device_using_its_id()
{
$repository = $this->getRepository();
$message = $this->createNewMessage();
$repository->save($message);
$theMessage = $repository->findById($message->id());
$this->assertEquals($message->id(), $theMessage->id());
}
abstract protected function getRepository();
Infrastructure tests
class InMemoryMessageRepository
implements MessageRepository
{
private $messages = [];
public function save(Message $message): void
{
$this->messages[$message->id()] = $message;
}
public function findById(string $id): Message
{
return $this->messages[$id];
}
}
Fake implementations
class DummyPrinter implements Printer
{
public function printTurn(Turn $turn): void
{
}
}
Dummy implementation
// services_test.yml
services:
MyProjectDomainMessageMessageRepository:
class: MyProjectInfrastructurePersistence
InMemoryInMemoryMessageRepository
MyProjectDomainPrinterPrinter:
class: TestsMyProjectDomainPrinterDummyPrinter
public: true
Test dependencies
Learned lessons
High confidence
Allows refactoring
Simplifies the code
Needs discipline
Take aways
Try to simplify the problem
Talk the business language
Automate from the beginning
Use different TDD strategies
Try some DDD patterns
Take aways
Thank you
Questions?
Luis Rovirosa
@luisrovirosa
www.codium.team

More Related Content

Similar to Learned lessons in a real world project by Luis Rovirosa at PHPMad

Google App Engine Developer - Day2
Google App Engine Developer - Day2Google App Engine Developer - Day2
Google App Engine Developer - Day2
Simon Su
 
BP101 - 10 Things to Consider when Developing & Deploying Applications in Lar...
BP101 - 10 Things to Consider when Developing & Deploying Applications in Lar...BP101 - 10 Things to Consider when Developing & Deploying Applications in Lar...
BP101 - 10 Things to Consider when Developing & Deploying Applications in Lar...
Martijn de Jong
 
Decomposing the Monolith using modern-day .NET and a touch of microservices
Decomposing the Monolith using modern-day .NET and a touch of microservicesDecomposing the Monolith using modern-day .NET and a touch of microservices
Decomposing the Monolith using modern-day .NET and a touch of microservices
Dennis Doomen
 
App engine devfest_mexico_10
App engine devfest_mexico_10App engine devfest_mexico_10
App engine devfest_mexico_10Chris Schalk
 
Google App Engine with Gaelyk
Google App Engine with GaelykGoogle App Engine with Gaelyk
Google App Engine with Gaelyk
Choong Ping Teo
 
Clean Architecture @ Taxibeat
Clean Architecture @ TaxibeatClean Architecture @ Taxibeat
Clean Architecture @ Taxibeat
Michael Bakogiannis
 
Cloudbase.io MoSync Reload Course
Cloudbase.io MoSync Reload CourseCloudbase.io MoSync Reload Course
Cloudbase.io MoSync Reload Course
cloudbase.io
 
inventory mangement project.pdf
inventory mangement project.pdfinventory mangement project.pdf
inventory mangement project.pdf
MeenakshiThakur86
 
Introduction to Polymer and Firebase - Simon Gauvin
Introduction to Polymer and Firebase - Simon GauvinIntroduction to Polymer and Firebase - Simon Gauvin
Introduction to Polymer and Firebase - Simon Gauvin
Simon Gauvin
 
Camunda BPM 7.2: Tasklist and Javascript Forms SDK (English)
Camunda BPM 7.2: Tasklist and Javascript Forms SDK (English)Camunda BPM 7.2: Tasklist and Javascript Forms SDK (English)
Camunda BPM 7.2: Tasklist and Javascript Forms SDK (English)
camunda services GmbH
 
The web - What it has, what it lacks and where it must go - keynote at Riga D...
The web - What it has, what it lacks and where it must go - keynote at Riga D...The web - What it has, what it lacks and where it must go - keynote at Riga D...
The web - What it has, what it lacks and where it must go - keynote at Riga D...
Robert Nyman
 
Symfony2 - from the trenches
Symfony2 - from the trenchesSymfony2 - from the trenches
Symfony2 - from the trenches
Lukas Smith
 
OWASP TOP 10 for PHP Programmers
OWASP TOP 10 for PHP ProgrammersOWASP TOP 10 for PHP Programmers
OWASP TOP 10 for PHP Programmers
rjsmelo
 
Droidcon Paris 2015
Droidcon Paris 2015Droidcon Paris 2015
Droidcon Paris 2015
Renaud Boulard
 
Mobile optimization
Mobile optimizationMobile optimization
Mobile optimization
purplecabbage
 
Max Voloshin - "Organization of frontend development for products with micros...
Max Voloshin - "Organization of frontend development for products with micros...Max Voloshin - "Organization of frontend development for products with micros...
Max Voloshin - "Organization of frontend development for products with micros...
IT Event
 
Beyond MVC: from Model to Domain
Beyond MVC: from Model to DomainBeyond MVC: from Model to Domain
Beyond MVC: from Model to Domain
Jeremy Cook
 
RTI Data-Distribution Service (DDS) Master Class 2011
RTI Data-Distribution Service (DDS) Master Class 2011RTI Data-Distribution Service (DDS) Master Class 2011
RTI Data-Distribution Service (DDS) Master Class 2011
Gerardo Pardo-Castellote
 
From framework coupled code to #microservices through #DDD /by @codelytv
From framework coupled code to #microservices through #DDD /by @codelytvFrom framework coupled code to #microservices through #DDD /by @codelytv
From framework coupled code to #microservices through #DDD /by @codelytv
CodelyTV
 
How We Built the Private AppExchange App (Apex, Visualforce, RWD)
How We Built the Private AppExchange App (Apex, Visualforce, RWD)How We Built the Private AppExchange App (Apex, Visualforce, RWD)
How We Built the Private AppExchange App (Apex, Visualforce, RWD)
Salesforce Developers
 

Similar to Learned lessons in a real world project by Luis Rovirosa at PHPMad (20)

Google App Engine Developer - Day2
Google App Engine Developer - Day2Google App Engine Developer - Day2
Google App Engine Developer - Day2
 
BP101 - 10 Things to Consider when Developing & Deploying Applications in Lar...
BP101 - 10 Things to Consider when Developing & Deploying Applications in Lar...BP101 - 10 Things to Consider when Developing & Deploying Applications in Lar...
BP101 - 10 Things to Consider when Developing & Deploying Applications in Lar...
 
Decomposing the Monolith using modern-day .NET and a touch of microservices
Decomposing the Monolith using modern-day .NET and a touch of microservicesDecomposing the Monolith using modern-day .NET and a touch of microservices
Decomposing the Monolith using modern-day .NET and a touch of microservices
 
App engine devfest_mexico_10
App engine devfest_mexico_10App engine devfest_mexico_10
App engine devfest_mexico_10
 
Google App Engine with Gaelyk
Google App Engine with GaelykGoogle App Engine with Gaelyk
Google App Engine with Gaelyk
 
Clean Architecture @ Taxibeat
Clean Architecture @ TaxibeatClean Architecture @ Taxibeat
Clean Architecture @ Taxibeat
 
Cloudbase.io MoSync Reload Course
Cloudbase.io MoSync Reload CourseCloudbase.io MoSync Reload Course
Cloudbase.io MoSync Reload Course
 
inventory mangement project.pdf
inventory mangement project.pdfinventory mangement project.pdf
inventory mangement project.pdf
 
Introduction to Polymer and Firebase - Simon Gauvin
Introduction to Polymer and Firebase - Simon GauvinIntroduction to Polymer and Firebase - Simon Gauvin
Introduction to Polymer and Firebase - Simon Gauvin
 
Camunda BPM 7.2: Tasklist and Javascript Forms SDK (English)
Camunda BPM 7.2: Tasklist and Javascript Forms SDK (English)Camunda BPM 7.2: Tasklist and Javascript Forms SDK (English)
Camunda BPM 7.2: Tasklist and Javascript Forms SDK (English)
 
The web - What it has, what it lacks and where it must go - keynote at Riga D...
The web - What it has, what it lacks and where it must go - keynote at Riga D...The web - What it has, what it lacks and where it must go - keynote at Riga D...
The web - What it has, what it lacks and where it must go - keynote at Riga D...
 
Symfony2 - from the trenches
Symfony2 - from the trenchesSymfony2 - from the trenches
Symfony2 - from the trenches
 
OWASP TOP 10 for PHP Programmers
OWASP TOP 10 for PHP ProgrammersOWASP TOP 10 for PHP Programmers
OWASP TOP 10 for PHP Programmers
 
Droidcon Paris 2015
Droidcon Paris 2015Droidcon Paris 2015
Droidcon Paris 2015
 
Mobile optimization
Mobile optimizationMobile optimization
Mobile optimization
 
Max Voloshin - "Organization of frontend development for products with micros...
Max Voloshin - "Organization of frontend development for products with micros...Max Voloshin - "Organization of frontend development for products with micros...
Max Voloshin - "Organization of frontend development for products with micros...
 
Beyond MVC: from Model to Domain
Beyond MVC: from Model to DomainBeyond MVC: from Model to Domain
Beyond MVC: from Model to Domain
 
RTI Data-Distribution Service (DDS) Master Class 2011
RTI Data-Distribution Service (DDS) Master Class 2011RTI Data-Distribution Service (DDS) Master Class 2011
RTI Data-Distribution Service (DDS) Master Class 2011
 
From framework coupled code to #microservices through #DDD /by @codelytv
From framework coupled code to #microservices through #DDD /by @codelytvFrom framework coupled code to #microservices through #DDD /by @codelytv
From framework coupled code to #microservices through #DDD /by @codelytv
 
How We Built the Private AppExchange App (Apex, Visualforce, RWD)
How We Built the Private AppExchange App (Apex, Visualforce, RWD)How We Built the Private AppExchange App (Apex, Visualforce, RWD)
How We Built the Private AppExchange App (Apex, Visualforce, RWD)
 

Recently uploaded

APIs for Browser Automation (MoT Meetup 2024)
APIs for Browser Automation (MoT Meetup 2024)APIs for Browser Automation (MoT Meetup 2024)
APIs for Browser Automation (MoT Meetup 2024)
Boni García
 
Orion Context Broker introduction 20240604
Orion Context Broker introduction 20240604Orion Context Broker introduction 20240604
Orion Context Broker introduction 20240604
Fermin Galan
 
2024 eCommerceDays Toulouse - Sylius 2.0.pdf
2024 eCommerceDays Toulouse - Sylius 2.0.pdf2024 eCommerceDays Toulouse - Sylius 2.0.pdf
2024 eCommerceDays Toulouse - Sylius 2.0.pdf
Łukasz Chruściel
 
Launch Your Streaming Platforms in Minutes
Launch Your Streaming Platforms in MinutesLaunch Your Streaming Platforms in Minutes
Launch Your Streaming Platforms in Minutes
Roshan Dwivedi
 
LORRAINE ANDREI_LEQUIGAN_HOW TO USE ZOOM
LORRAINE ANDREI_LEQUIGAN_HOW TO USE ZOOMLORRAINE ANDREI_LEQUIGAN_HOW TO USE ZOOM
LORRAINE ANDREI_LEQUIGAN_HOW TO USE ZOOM
lorraineandreiamcidl
 
GOING AOT WITH GRAALVM FOR SPRING BOOT (SPRING IO)
GOING AOT WITH GRAALVM FOR  SPRING BOOT (SPRING IO)GOING AOT WITH GRAALVM FOR  SPRING BOOT (SPRING IO)
GOING AOT WITH GRAALVM FOR SPRING BOOT (SPRING IO)
Alina Yurenko
 
Graspan: A Big Data System for Big Code Analysis
Graspan: A Big Data System for Big Code AnalysisGraspan: A Big Data System for Big Code Analysis
Graspan: A Big Data System for Big Code Analysis
Aftab Hussain
 
Utilocate provides Smarter, Better, Faster, Safer Locate Ticket Management
Utilocate provides Smarter, Better, Faster, Safer Locate Ticket ManagementUtilocate provides Smarter, Better, Faster, Safer Locate Ticket Management
Utilocate provides Smarter, Better, Faster, Safer Locate Ticket Management
Utilocate
 
Enterprise Resource Planning System in Telangana
Enterprise Resource Planning System in TelanganaEnterprise Resource Planning System in Telangana
Enterprise Resource Planning System in Telangana
NYGGS Automation Suite
 
OpenMetadata Community Meeting - 5th June 2024
OpenMetadata Community Meeting - 5th June 2024OpenMetadata Community Meeting - 5th June 2024
OpenMetadata Community Meeting - 5th June 2024
OpenMetadata
 
Navigating the Metaverse: A Journey into Virtual Evolution"
Navigating the Metaverse: A Journey into Virtual Evolution"Navigating the Metaverse: A Journey into Virtual Evolution"
Navigating the Metaverse: A Journey into Virtual Evolution"
Donna Lenk
 
A Sighting of filterA in Typelevel Rite of Passage
A Sighting of filterA in Typelevel Rite of PassageA Sighting of filterA in Typelevel Rite of Passage
A Sighting of filterA in Typelevel Rite of Passage
Philip Schwarz
 
Introducing Crescat - Event Management Software for Venues, Festivals and Eve...
Introducing Crescat - Event Management Software for Venues, Festivals and Eve...Introducing Crescat - Event Management Software for Venues, Festivals and Eve...
Introducing Crescat - Event Management Software for Venues, Festivals and Eve...
Crescat
 
Mobile App Development Company In Noida | Drona Infotech
Mobile App Development Company In Noida | Drona InfotechMobile App Development Company In Noida | Drona Infotech
Mobile App Development Company In Noida | Drona Infotech
Drona Infotech
 
Atelier - Innover avec l’IA Générative et les graphes de connaissances
Atelier - Innover avec l’IA Générative et les graphes de connaissancesAtelier - Innover avec l’IA Générative et les graphes de connaissances
Atelier - Innover avec l’IA Générative et les graphes de connaissances
Neo4j
 
GraphSummit Paris - The art of the possible with Graph Technology
GraphSummit Paris - The art of the possible with Graph TechnologyGraphSummit Paris - The art of the possible with Graph Technology
GraphSummit Paris - The art of the possible with Graph Technology
Neo4j
 
Artificia Intellicence and XPath Extension Functions
Artificia Intellicence and XPath Extension FunctionsArtificia Intellicence and XPath Extension Functions
Artificia Intellicence and XPath Extension Functions
Octavian Nadolu
 
Automated software refactoring with OpenRewrite and Generative AI.pptx.pdf
Automated software refactoring with OpenRewrite and Generative AI.pptx.pdfAutomated software refactoring with OpenRewrite and Generative AI.pptx.pdf
Automated software refactoring with OpenRewrite and Generative AI.pptx.pdf
timtebeek1
 
Top Features to Include in Your Winzo Clone App for Business Growth (4).pptx
Top Features to Include in Your Winzo Clone App for Business Growth (4).pptxTop Features to Include in Your Winzo Clone App for Business Growth (4).pptx
Top Features to Include in Your Winzo Clone App for Business Growth (4).pptx
rickgrimesss22
 
AI Fusion Buddy Review: Brand New, Groundbreaking Gemini-Powered AI App
AI Fusion Buddy Review: Brand New, Groundbreaking Gemini-Powered AI AppAI Fusion Buddy Review: Brand New, Groundbreaking Gemini-Powered AI App
AI Fusion Buddy Review: Brand New, Groundbreaking Gemini-Powered AI App
Google
 

Recently uploaded (20)

APIs for Browser Automation (MoT Meetup 2024)
APIs for Browser Automation (MoT Meetup 2024)APIs for Browser Automation (MoT Meetup 2024)
APIs for Browser Automation (MoT Meetup 2024)
 
Orion Context Broker introduction 20240604
Orion Context Broker introduction 20240604Orion Context Broker introduction 20240604
Orion Context Broker introduction 20240604
 
2024 eCommerceDays Toulouse - Sylius 2.0.pdf
2024 eCommerceDays Toulouse - Sylius 2.0.pdf2024 eCommerceDays Toulouse - Sylius 2.0.pdf
2024 eCommerceDays Toulouse - Sylius 2.0.pdf
 
Launch Your Streaming Platforms in Minutes
Launch Your Streaming Platforms in MinutesLaunch Your Streaming Platforms in Minutes
Launch Your Streaming Platforms in Minutes
 
LORRAINE ANDREI_LEQUIGAN_HOW TO USE ZOOM
LORRAINE ANDREI_LEQUIGAN_HOW TO USE ZOOMLORRAINE ANDREI_LEQUIGAN_HOW TO USE ZOOM
LORRAINE ANDREI_LEQUIGAN_HOW TO USE ZOOM
 
GOING AOT WITH GRAALVM FOR SPRING BOOT (SPRING IO)
GOING AOT WITH GRAALVM FOR  SPRING BOOT (SPRING IO)GOING AOT WITH GRAALVM FOR  SPRING BOOT (SPRING IO)
GOING AOT WITH GRAALVM FOR SPRING BOOT (SPRING IO)
 
Graspan: A Big Data System for Big Code Analysis
Graspan: A Big Data System for Big Code AnalysisGraspan: A Big Data System for Big Code Analysis
Graspan: A Big Data System for Big Code Analysis
 
Utilocate provides Smarter, Better, Faster, Safer Locate Ticket Management
Utilocate provides Smarter, Better, Faster, Safer Locate Ticket ManagementUtilocate provides Smarter, Better, Faster, Safer Locate Ticket Management
Utilocate provides Smarter, Better, Faster, Safer Locate Ticket Management
 
Enterprise Resource Planning System in Telangana
Enterprise Resource Planning System in TelanganaEnterprise Resource Planning System in Telangana
Enterprise Resource Planning System in Telangana
 
OpenMetadata Community Meeting - 5th June 2024
OpenMetadata Community Meeting - 5th June 2024OpenMetadata Community Meeting - 5th June 2024
OpenMetadata Community Meeting - 5th June 2024
 
Navigating the Metaverse: A Journey into Virtual Evolution"
Navigating the Metaverse: A Journey into Virtual Evolution"Navigating the Metaverse: A Journey into Virtual Evolution"
Navigating the Metaverse: A Journey into Virtual Evolution"
 
A Sighting of filterA in Typelevel Rite of Passage
A Sighting of filterA in Typelevel Rite of PassageA Sighting of filterA in Typelevel Rite of Passage
A Sighting of filterA in Typelevel Rite of Passage
 
Introducing Crescat - Event Management Software for Venues, Festivals and Eve...
Introducing Crescat - Event Management Software for Venues, Festivals and Eve...Introducing Crescat - Event Management Software for Venues, Festivals and Eve...
Introducing Crescat - Event Management Software for Venues, Festivals and Eve...
 
Mobile App Development Company In Noida | Drona Infotech
Mobile App Development Company In Noida | Drona InfotechMobile App Development Company In Noida | Drona Infotech
Mobile App Development Company In Noida | Drona Infotech
 
Atelier - Innover avec l’IA Générative et les graphes de connaissances
Atelier - Innover avec l’IA Générative et les graphes de connaissancesAtelier - Innover avec l’IA Générative et les graphes de connaissances
Atelier - Innover avec l’IA Générative et les graphes de connaissances
 
GraphSummit Paris - The art of the possible with Graph Technology
GraphSummit Paris - The art of the possible with Graph TechnologyGraphSummit Paris - The art of the possible with Graph Technology
GraphSummit Paris - The art of the possible with Graph Technology
 
Artificia Intellicence and XPath Extension Functions
Artificia Intellicence and XPath Extension FunctionsArtificia Intellicence and XPath Extension Functions
Artificia Intellicence and XPath Extension Functions
 
Automated software refactoring with OpenRewrite and Generative AI.pptx.pdf
Automated software refactoring with OpenRewrite and Generative AI.pptx.pdfAutomated software refactoring with OpenRewrite and Generative AI.pptx.pdf
Automated software refactoring with OpenRewrite and Generative AI.pptx.pdf
 
Top Features to Include in Your Winzo Clone App for Business Growth (4).pptx
Top Features to Include in Your Winzo Clone App for Business Growth (4).pptxTop Features to Include in Your Winzo Clone App for Business Growth (4).pptx
Top Features to Include in Your Winzo Clone App for Business Growth (4).pptx
 
AI Fusion Buddy Review: Brand New, Groundbreaking Gemini-Powered AI App
AI Fusion Buddy Review: Brand New, Groundbreaking Gemini-Powered AI AppAI Fusion Buddy Review: Brand New, Groundbreaking Gemini-Powered AI App
AI Fusion Buddy Review: Brand New, Groundbreaking Gemini-Powered AI App
 

Learned lessons in a real world project by Luis Rovirosa at PHPMad