Monolithe modulaire
Pourquoi ? Comment ?

Symfony Live 2020 Paris

Timothée Barray

twitter.com/timbarray
github.com/tyx

Agenda

  • Quels problèmes ?
  • Pourquoi un monolithe modulaire ?

  • Comment bien démarrer

  • Comment reprendre le contrôle d'un legacy

Quels problèmes ?

data-driven killed us

Data Everywhere

table user

id 123
username tyx
email timbarray@gmail.com
firstname Tim
lastname Barray
address 10 rue rivoli 75001
password mqlsmdqdlkq
phonenumber 0606060909
role [ROLE_ADMIN]

Blog

Identity

DRY killed us

Besoin de construire des logiciels qui peuvent être modifiés continuellement.


Aussi important que la construction d’un logiciel qui fonctionne.

Les besoins vont changer c’est sûr !

 

Pourquoi un

monolithe modulaire ?

Microservices

Réduire la boucle

de feedback

Agilité  = capacité de changer les choses rapidement

Autonomie des modules

Comment bien démarrer ?

Un dev devant son /src

Exploration du métier

Travail d'équipe

Concentrez vous sur l'essentiel

Un dossier par module

La règle :

Aucun appel à une classe

d'un autre module

Deptrac

Comment reconnaitre une frontière ?

Une frontière est traversée quand un concept avec le même nom n’a plus la même utilité.

Laissez-vous du temps

Contrôler nos ouvertures

Ouvrir un module

Privé

Public

Contrat

Liberté

Exemple

Payment propose

Basket contractualise son besoin

PaymentService::pay(int $amount): PaymentResult
PaymentGateway::pay(int $amount): bool
PaymentModuleGateway::pay(int $amount): bool

Basket implémente son besoin via la proposition de Payment

ACL : Anti Corruption Layer

CI Failure

Collaboration Allowlist

Frontières

Ouvertures

Un stockage dédié par module

IdentityProvider

Reservation

id 123
username tyx
email timbarray@gmail.com

table user

id 456
check_in_at 2020-09-23 15:00
check_out_at 2020-09-24 10:00
nb_guests 1
booker_id 123

table reservation

Migration microservice clé en main

ajout code avec implem http

<?php declare(strict_types=1);

namespace Vendor\Basket\Infra;

use Vendor\Basket\Domain\PaymentGateway;
use Symfony\Contracts\HttpClient\HttpClientInterface;

final class HttpPaymentGateway implements PaymentGateway
{
    private HttpClientInterface $httpClient;

    public function __construct(HttpClientInterface $httpClient)
    {
        $this->httpClient = $httpClient;
    }

    public function pay(int $amount): bool
    {
        $json = $this->httpClient->request('POST', '/pay', ['json' => [
            'amount' => $amount
        ]])->toArray();

        return 'ok' === $json['status'] ?? false;
    }
}

Merci Symfony

<?php declare(strict_types=1);

namespace Gogaille;

use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;


class Kernel extends BaseKernel
{
    use MicroKernelTrait;
    
    protected function configureContainer(ContainerConfigurator $container): void
    {
        $container->import('../config/{packages}/*.yaml');
        $container->import("../config/{packages}/{$this->environment}/*.yaml");
        $container->import('../config/{services}.yaml');
        $container->import('./*/Resources/{services}.yaml');
        $container->import("../config/{services}_{$this->environment}.yaml");
        $container->import("./*/Resources/{services}_{$this->environment}.yaml");
    }

    protected function configureRoutes(RoutingConfigurator $routes): void
    {
        $routes->import("../config/{routes}/{$this->environment}/*.yaml");
        $routes->import('../config/{routes}/*.yaml');
        $routes->import('../config/{routes}.yaml');
        $routes->import('./*/Resources/routes/*.yaml');
    }
}

Reprendre le contrôle de votre legacy

appliquer de la modularité petit à petit

Fréquence de modification

Criticité

critique et changeant

critique et stable

peu critique et changeant

Pas d'urgence

Super pour commencer

Objectif principal

On arrête d'écrire du legacy

Stratégies

Créer une bulle

<?php

if ($form['payment_mean'] === 'cb') {
	$this->legacyPayment->pay();
} else {
	$this->multiPayment->pay();
}

Basket

Paiement

MultiPaiment

Bulle

ACL

ACL

Bulle

MultiPaiment

ACL

ACL

Paiement

nouvelle db

legacy db

Event Driven

Règles du

Monolithe club

  1. On contractualise les appels inter modules
  2. On utilise un ACL pour passer d'un module à un autre
  3. On dédie un espace de stockage à nos modules

Soyez fier de votre monolithe

Merci

  • https://www.domainlanguage.com/wp-content/uploads/2016/04/GettingStartedWithDDDWhenSurroundedByLegacySystemsV1.pdf
  • https://understandlegacycode.com/blog/avoid-rewriting-a-legacy-system-from-scratch-by-strangling-it/
  • https://verraes.net/2014/08/dry-is-about-knowledge/
  • http://tpierrain.blogspot.com/2020/04/et-si-les-adaptateurs-faisaient-eux.html
  • https://www.lilobase.me/votre-application-a-besoin-de-son-jardin-secret-attention-a-la-localite-de-linformation/
  • https://github.com/kgrzybek/modular-monolith-with-ddd

Bibliographie

Monolithe modulaire, Pourquoi ? Comment ?

By Timothée Barray

Monolithe modulaire, Pourquoi ? Comment ?

Symfony Live 2020 - Paris - 23 septembre 2020

  • 3,787