Molto spesso capita di venire a contatto con applicazioni legacy piuttosto datate, i classici monoliti che sono cresciuti a dismisura nel tempo accumulando debito tecnologico.
A causa delle priorità di business delle aziende, non sempre si riesce ad allocare il budget e il tempo necessario per iniziare subito il processo di ristrutturazione architetturale e di rimodellazione dei dati che servirebbe.
In questo talk presenterò una soluzione che mi è capitato di adottare recentemente per iniziare a ridefinire la struttura di un progetto legacy, utilizzando un approccio basato su Domain Driven Design, architettura esagonale e l’utilizzo di PHP senza framework.
Vedremo come è stato creato un nuovo servizio “satellite” da zero, come sono state implementate le componenti principali, come si è tenuto il codice legacy ai margini dell’applicazione, come si è approcciato il testing, il tutto nell’ottica di poter spacchettare il monolite in microservizi in un secondo momento.
3. Some context
● A start-up born 10 years ago
● Initial business was focused on public transportation services
● At some point the company saw a market opportunity and
started to focus on the so-called “flexible benefits”
● Business grew fast, the domain evolved
7. A Big Ball of Mud is a haphazardly
structured, sprawling, sloppy,
duct-tape-and-baling-wire, spaghetti-code
jungle. These systems show unmistakable
signs of unregulated growth, and repeated,
expedient repair.
Information is shared promiscuously
among distant elements of the system,
often to the point where nearly all the
important information becomes global or
duplicated.
9. ● Spaghetti code
● Leaky abstractions
● A few services with lots of
responsibilities
● All services read from and
write to the same DB
● External BI tools connected
directly to the DB
10. Our concerns
● Legacy code is really difficult to test
● A lot of bad duplication (that leads to shotgun surgery)
● Inconsistent naming
● A single DB everyone has access to
● DB performance / slow queries
● Lots of processes not automated yet
● High maintenance cost
● ...
11. We had already started to extract some services
in order to separate responsibilities and keep
complexity under control...
12.
13. Tech needs VS Business needs
● Improve architecture design
● Migrate old services to
Kubernetes cluster
● Cleanup code
● Find proper names to
software components
● Find better abstractions
● Add more tests
● Split the main monolith into
microservices
● ...
● Lots of things I don’t
remember well
● The e-commerce portal has
a bad UX and looks old
compared with some
competitors
15. A chance for a new design?
PROS:
Portal was a good starting point for refactoring:
● It included a large part of the domain
● Mainly data presentation
● Not much business operations
CONS:
● Large scope
● Timebox
16.
17. How to model the domain
while dealing with legacy?
● DDD
● Hexagonal architecture
18. Domain Driven Design
Three core principles:
● Focus on the core domain and domain logic
● Base complex designs on models of the domain
● Constantly collaborate with domain experts, in order to
improve the application model and resolve any emerging
domain-related issues.
19. Hexagonal Architecture
● Dependency inversion principle: both low level and high level
modules should depend on abstractions, abstractions should
not depend on details.
● Use adapters as a translation level, so you can have a ”pure”
representation of the domain in the application core
● Keep implementation details at the borders of the
application
● Loosely coupled application components, components are
exchangeable, high testability
● As we depend on abstractions, we can change the
implementation in the future without changing the core
23. Repositories vs Services
Why did we use repositories and not services for retrieving data
from the legacy core?
● No much logic to handle, only data extraction
● For operations with side-effects we actually used services
(e.g. purchase operation)
● We wanted to explicitly state which were our data and define
their shape
● Once we move data on the correct side, we can simply add
new repositories implementation
● The interfaces will be our contract/specification in this
migration process
24. Un utente usa 10 euro del suo benefit per richiedere un
servizio.
An employee takes 10 euro from his wallet and orders a
product.
29. Hexagonal architecture with framework
● High coupling between DB schema and codebase
● Model with active record (Laravel)
● Entity + Repository (Symfony)
● Default dir structure organize classes by component role
(model, controller, etc)
● Global facades accessible everywhere, implicit dependencies
(you could use DI, but it’s a matter of discipline)
● Low maintainability because of large classes scope and
components coupling
30. Hexagonal architecture without framework
● DB schema can be decoupled from domain logic
● Dir structure organize classes by domain logic (product,
wallet, employee)
● Interfaces + value object/entities + repositories
● Explicit dependencies by enforcing DI
● Apply SOLID principles, limit classes scope, increase
maintainability
33. PHP without framework: how to
What we need:
● HTTP (Routing, Middlewares, ...)
● Logging
● Configuration
● Dependency Injection Container
● Error handling
44. Let’s put things together
How to bootstrap:
- Create a new DI container
- Import all definitions (logger and configuration provider are
defined there)
- Create a new Slim app with the container injected
- Set the default app error handler
- Apply routes definitions
- Run app
45. Testing
We have:
● Enforced explicit dependency injection
● Defined interfaces for services, repositories, loggers, etc
46. Testing
● Mock dependencies of the class under test
● Create the class instance by injecting the mocks in the
container
● Test only the class behaviour in isolation
● In integration use in-memory implementations for faster
tests
● In the DI container make LoggerInterface resolve to a
TestLogger and test also logging
47. Next steps?
● Continue the refactoring of legacy core
● Define new bounded contexts following a DDD approach
● Separate responsibilities, split core monolith into different
services
● As we extract services or move data from a DB to another,
update/add repositories implementation on Portal without
changing the interfaces
48. What actually happened
● The company was acquired by a competitor
● The competitor had a similar portal
● They decided to maintain our legacy system while migrating
users to their systems
● “Portal” project was dismissed
49. What I learned
● How to build a solid foundation for a PHP application without
framework (it’s quite easy)
● Thinking “frameworkless” helps you to shape your
application starting from scratch, adding only what you need
● Hexagonal architecture is a great architectural pattern, less
coupling between application components, more
maintainability, more testability.
50. Thanks for your attention!
fabiopellegrini90@gmail.com
linkedin.com/in/fabiopellegrini90