際際滷

際際滷Share a Scribd company logo
Learned lessons in
real world projects
Jordi Anguela
@jordianguela
www.codium.team
About us
+10 years doing web
applications
Team transformations
Lean & Craftsmanship
Why this
presentation?
Automate everything
How to deal with a new project
Context
Domain at heart
Agenda
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 dont know each other
We dont know our clients business
Common problems
starting a project
Difficult to estimate: time and money
10-100 pages
1 or more requirement documents
All must be done
What we usually receive from the client
Everything is important
Vertical slices
Split into Sprints
User stories
Our recipe: the Sprint 0
Iterative / Reduce scope
Prioritize
Ubiquitous language
Second sprint - Support final hardware
First sprint - Minimal functionality
1 counter call unit, 1 ticket dispenser & 1 queue status display.
No services
No printing real tickets
Neither users nor roles
Third sprint - Services & basic actions
How we break it in sprints
Fourth sprint - Roles & permissions
Why?
To talk the same language as business
To avoid having the same concept with
different names around the project
To be rigorous
Ubiquitous language
How?
Ubiquitous language
Learned lessons
Dealing with a new project
Sprint 0 is the key
Simplify the problem
Prioritize
Manage expectations
use Business names
How we develop
Ensure quality
Automate everything
Domain at heart
How we develop
Automate everything
Automate everything
Why?
Because the systems are complex
Difficult to setup and update
To reduce costs avoid repetitive tasks
Ensure quality
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 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"
],
Automated test execution
// bitbucket-pipelines.yml
image: composer:latest
pipelines:
branches:
master:
- step:
caches:
- composer
script:
- composer install
- composer tests:unit
definitions:
caches:
composer: vendor
Force tests execution before each commit
Tired of breaking the Pipelines?
Step-by-step guide:
1. Being at the root of a git repository
2. Add pre-commit file at .git/hooks and add the
following content:
#! /bin/sh
<your command to run the tests>
exit $?
3. Make the file executable
Learned lessons
Automating everything
Easy setup/update
Documentation
Double check on tests
Domain at heart
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());
}
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
Domain and Infrastructure
Domain Interface
Infrastructure implementation
DDD learned lessons
Ubiquitous language works
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
Testing an interface
MessageRepositoryTest
+ test1
+ test2
+ test3
+ abstract getRepository()
InMemoryMessageRepositoryTest
+ getRepository()
DoctrineMessageRepositoryTest
+ getRepository()
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
Ensuring quality
High confidence
Allows refactoring
Simplifies the code
Needs discipline
Without 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?
Jordi Anguela
@jordianguela
www.codium.team

More Related Content

Learned lessons in real world projects by Jordi Anguela at Mallorca Software Craftsmanship