ºÝºÝߣ

ºÝºÝߣShare 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

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