Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Shrek, Onions and Architecture - PHPDay 2024

Shrek, Onions and Architecture - PHPDay 2024

Have you ever encountered a codebase that's ogreish and has so many layers of complexity that it makes you cry? It might actually be an onion! In this humorous talk we'll investigate the structure of large monolithic applications and how we can peel back layers to reveal greater understanding. Using the principles of hexagonal architecture and the science of onion growth, we can make monoliths great again.

Katy Ereira

May 16, 2024
Tweet

More Decks by Katy Ereira

Other Decks in Programming

Transcript

  1. Katy Ereira - #PHPDay24 - @maccath@phpc.social It’s only logical…. •

    There are so many layers. • It smells (or at least, engineers act like it does). • Biting into it without preparation leaves a bad taste in your mouth. • It makes you cry. Introducing: the monionlith
  2. Katy Ereira - #PHPDay24 - @maccath@phpc.social Agenda 01 Growing onions

    Learn the secret to a monionlith’s robustness, from the ground up. 02 Cooking onions Making monionliths palatable - top tips for coding without tears. 00 Peeling onions Demystifying a mononiolith’s anatomy and its architectural layers.
  3. Katy Ereira - #PHPDay24 - @maccath@phpc.social The layers of an

    onion can be separated one at a time, from the outside in. 00 Hexagonal architecture and dependency inversion
  4. Katy Ereira - #PHPDay24 - @maccath@phpc.social 1 An onion. Skin:

    application layer Flesh: business logic Base: core domain model Roots: infrastructure Leaves: presentation 2 3 4 5 1 2 3 4 5
  5. Katy Ereira - #PHPDay24 - @maccath@phpc.social Flowers and leaves •

    Concerned with visibility and accessibility from the outside. • Provides entry points for interacting with the internals. • Is attached to and aware of all inner layers. presentation 🧅 Fun onion fact: onions usually flower in the second year as they move between growth and reproductive phases.
  6. Katy Ereira - #PHPDay24 - @maccath@phpc.social ➔ HTTP routes ➔

    REST API entry points ➔ GraphQL schemas ➔ HTML templates / views Flowers and leaves presentation
  7. Katy Ereira - #PHPDay24 - @maccath@phpc.social Skin • Facilitates safe

    interactions with the system. • It’s thin; does not provide any business logic. • Receives and directs requests, and returns responses. application layer 🧅 Fun onion fact: dried leaves which form the skin of an onion are also known as the ‘tunic’, and it protects the onion from pests and disease.
  8. Katy Ereira - #PHPDay24 - @maccath@phpc.social Fleshy layers • Form

    the bulk of the system; provides business logic. • Are distinct and separate from each other. • Are implementations of core abstractions. business logic 🧅 Fun onion fact: the fleshy layers that form the bulk of an onion are a type of succulent leaf that stores water and nutrients.
  9. Katy Ereira - #PHPDay24 - @maccath@phpc.social ➔ Domain services ➔

    Helpers ➔ Processes / algorithms Fleshy layers business logic
  10. Katy Ereira - #PHPDay24 - @maccath@phpc.social Base • Contains the

    building blocks for all future growth. • Encapsulates core concepts. • Has no substance in and of itself. core domain model 🧅 Fun onion fact: the base of an onion is actually an extremely short and wide stem, and it contains stem cells.
  11. Katy Ereira - #PHPDay24 - @maccath@phpc.social Roots • Communicates outwards

    from the system. • Interacts with external infrastructure. • Is well hidden from the public. • Enriches the system internals. infrastructure 🧅 Fun onion fact: onion roots grow from the underside of the base stem in contrast to leaves and flowers which grow from the top.
  12. Katy Ereira - #PHPDay24 - @maccath@phpc.social ➔ SDKs ➔ Databases

    & ORMs ➔ Frameworks ➔ Adaptors Roots infrastructure
  13. Katy Ereira - #PHPDay24 - @maccath@phpc.social Onions grow from the

    basal stem whose cells contain the information for growth. 01 Domain driven design.
  14. Katy Ereira - #PHPDay24 - @maccath@phpc.social Growing onions A step

    by step guide Surveyance Talk to domain experts and business stakeholders to gain understanding and become fluent in Ubiquitous Language . Write User Stories to encapsulate the problems your system aims to solve. Define API Schemas . Groundwork Plant a seed Germinate Set root Develop a skin Bloom and harvest!
  15. Katy Ereira - #PHPDay24 - @maccath@phpc.social Growing onions A step

    by step guide Encapsulation A seed contains cells that have instructions required for growth; but they themselves are small. Define Entities and Value Objects , making sure to speak Ubiquitous Language. This is your Core Domain Model . Groundwork Plant a seed Germinate Set root Develop a skin Bloom and harvest!
  16. Katy Ereira - #PHPDay24 - @maccath@phpc.social Growing onions A step

    by step guide Emergence Now we implement business logic. This defines how domain objects transact. Onions grow in layers. Each layer is an independent Bounded Context . Bounded contexts should only reference the Core Domain Model. Groundwork Plant a seed Germinate Set root Develop a skin Bloom and harvest!
  17. Katy Ereira - #PHPDay24 - @maccath@phpc.social Growing onions A step

    by step guide Integration External infrastructure supports growth. Our domain base provides interface Ports that can be implemented as Adaptors for specific infrastructure. We can add adaptors for ORMs, messaging and event services, and logging. Groundwork Plant a seed Germinate Set root Develop a skin Bloom and harvest!
  18. Katy Ereira - #PHPDay24 - @maccath@phpc.social Growing onions A step

    by step guide Application It’s time to put everything together: user stories become Use Cases via controllers. Adaptors are plugged into ports by using Service Providers . Groundwork Plant a seed Germinate Set root Develop a skin Bloom and harvest!
  19. Katy Ereira - #PHPDay24 - @maccath@phpc.social Growing onions A step

    by step guide Bloom! In this final stage, add routes and register API schema definitions. Implement graphical user interfaces, views and templates - this is the Frontend . Groundwork Plant a seed Germinate Set root Develop a skin Bloom and harvest!
  20. Katy Ereira - #PHPDay24 - @maccath@phpc.social One does not simply

    bite into an onion. One must master the elements of good cooking. 02 Coding without tears.
  21. Katy Ereira - #PHPDay24 - @maccath@phpc.social 🧂 Salt • Add

    comments - but only when they improve clarity. • When naming, use Ubiquitous Language. • Reference explicit types wherever possible. • Don’t go overboard. add clarity 󰱜 Samin says, “The three basic decisions involving salt are: When? How much? In what form?”
  22. Katy Ereira - #PHPDay24 - @maccath@phpc.social /** * @throws NotFound

    */ public function getBar( Foo $foo, ): Bar; Don’t do that Do this /** * @param Foo $foo * @return Bar */ public function getBar( Foo $foo, ): Bar;
  23. Katy Ereira - #PHPDay24 - @maccath@phpc.social interface Checkout { public

    function submit( Order $order, ); } interface Order { /** * @return Item[] */ public function getItems(): array; public function getCustomer(): Customer; } Don’t do that Do this // Processes orders interface Handler { /** * Submit an order * @param Order|object|array $arg */ public function process( mixed $arg, ); } interface Order { // Contains customer and items. public function props(): object; }
  24. Katy Ereira - #PHPDay24 - @maccath@phpc.social public function useCase(): void;

    /** * @throws UpdateFailed */ private function updateFoo(Foo $foo): Foo; /** * @throws EventFailed */ private function emitEvent(): void; Don’t do that Do this /** * This actually does 2 things: * first it updates Foo * then it maybe emits a Bar event. * * Returns the Foo if successful. * Else it will return an error string. */ public function doThing(): mixed;
  25. Katy Ereira - #PHPDay24 - @maccath@phpc.social 🧈 Fat • Don’t

    define unnecessary get and set methods. • Consider whether attributes are optional or required. • Beware of invalid state. • If something is mutable; think about why. keep it lean 󰱜 Samin says, “fat plays three distinct roles in the kitchen: as a main ingredient, as a cooking medium, and … as seasoning.”
  26. Katy Ereira - #PHPDay24 - @maccath@phpc.social class User { public

    function __construct( private readonly UUID $id, private readonly string $name, ); public function getId(): UUID; public function getName(): string; } Don’t do that Do this class User { public function getId(): UUID; public function setId(UUID $id); public function getName(): string; public function setName(string $name); }
  27. Katy Ereira - #PHPDay24 - @maccath@phpc.social interface Phone {} interface

    Address {} interface ContactDetails { public function getAddress(): Address; public function setAddress( Address $address, ); public function getPhone(): Phone; public function setPhone( Phone $phone ); } Don’t do that Do this interface ContactDetails { public function getAddress(): string; public function setAddress( string $address, ); public function getZip(): string; public function setZip( string $zip, ); public function getCountry(): string; public function setCountry( string $country, ); public function getPhone(): string; public function setPhone( string $phoneNumber, ); }
  28. Katy Ereira - #PHPDay24 - @maccath@phpc.social interface Person { public

    function getAge(): ?int; public function setBirthDate( DateTime $birthdate, ): void; } Don’t do that Do this interface Person { public function getBirthDate(): ?DateTime; public function setBirthDate( DateTime $birthdate, ): void; public function getAge(): ?int; public function setAge(int $age): void; }
  29. Katy Ereira - #PHPDay24 - @maccath@phpc.social interface Project { public

    function isPublished(): bool; public function publish(): self; public function archive(): self; } Don’t do that Do this interface Project { public function getStatus(): Status; public function setStatus( Status $status ): void; }
  30. Katy Ereira - #PHPDay24 - @maccath@phpc.social 🍶 Acid • Check

    that use statements only reference inner layers. • Depend on abstractions, not concretions. • Don’t reference specific infrastructure. • To break rules you must first understand them. manage complexity 󰱜 Samin says, “play to each element’s strengths: use Salt to enhance, Fat to carry, and Acid to balance flavor.”
  31. Katy Ereira - #PHPDay24 - @maccath@phpc.social namespace YourApp\Domain; interface Foo

    { public function getBar(): Bar; } interface Bar {} Don’t do that Do this namespace YourApp\Domain; use YourApp\Services; interface Foo { public function getBar(): Services\Bar; } namespace YourApp\Services; class Bar {} namespace YourApp\Services; use YourApp\Domain; class Bar implements Domain\Bar {}
  32. Katy Ereira - #PHPDay24 - @maccath@phpc.social namespace MyApp\Services; use MyApp\Domain;

    class Foo implements Domain\Foo { private Domain\Repository $repo; public function getFirstBar(): Domain\Bar { return $this->repo ->getBars() ->first(); } } Don’t do that Do this namespace MyApp\Services; use MyApp\Domain; use MySQL\Connection; class Foo implements Domain\Foo { private Connection $mysql; public function getFirstBar(): Domain\Bar { return new Bar( $this->mysql->execute( ‘SELECT * FROM bars LIMIT 1’, ), ); } }
  33. Katy Ereira - #PHPDay24 - @maccath@phpc.social namespace MyApp\Infrastructure; use MyApp\Domain;

    class MySQLRepo implements Domain\Repo { public function __construct( private readonly MySQL\Connection $sql ) { } public function getBars(): array { return $this->sql->execute( ‘SELECT * FROM bars’, ); } } Don’t do that Do this namespace MyApp\Infrastructure; use MyApp\Domain; class MySQLRepo implements Domain\Repo { public function __construct() { $this->sql = new MySQL\Connection(); } public function getBars(): array { return $this->sql->execute( ‘SELECT * FROM bars’, ); } }
  34. Katy Ereira - #PHPDay24 - @maccath@phpc.social 🔥 Heat • Test

    implementations, not abstractions. • Avoid using mocks unless absolutely necessary. Use fake implementations. • Implementation details are subject to change; don’t test private methods. perform under pressure 󰱜 Samin says, “apply the right type and quantity of heat for the proper amount of time ... and you will turn out vibrant and beautiful food”
  35. Katy Ereira - #PHPDay24 - @maccath@phpc.social public function testGetBar(): void

    { $sut = new Foo(); $sut->baz = Mockery::mock(Baz::class); $baz->expects(‘check’) ->andReturnTrue(); $this->assertNotNull($sut->getBar()); } Don’t do that Do this public function testGetBar(): void { $sut = Mockery::mock(Foo::class); $bar = Mockery::mock(Bar::class); $sut->expects(‘getBar’) ->andReturn($bar); $this->assertNotNull($sut->getBar()); } public function getBar(): ?Bar { return $this->baz->check() ? new Bar() : null; }
  36. Katy Ereira - #PHPDay24 - @maccath@phpc.social public function testGetBar(): void

    { $sut = new Foo(); $sut->baz = new class implements Baz { public function check(): bool { return true; } }; $this->assertNotNull($sut->getBar()); } Don’t do that Do this public function testGetBar(): void { $sut = Mockery::mock(Foo::class); $bar = Mockery::mock(Bar::class); $sut->expects(‘getBar’) ->andReturn($bar); $this->assertNotNull($sut->getBar()); } public function getBar(): ?Bar { return $this->baz->check() ? new Bar() : null; }
  37. Katy Ereira - #PHPDay24 - @maccath@phpc.social Quick recap then that

    shallot! Onion Architecture Your application has many separate layers, accessed from the outside in. ✔ Identify the layers in your system - install Deptrac! DDD How your application grows depends a lot on the domain in which it is planted. ✔ Start from the ground for new functionality. Got Feedback? Katy Ereira @maccath@phpc.social joind.in/talk/1bb9b Clean Coding Applications taste better when you master the elements of good coding. ✔ Delete redundant comments. Refactor to use DI.