Slide 1

Slide 1 text

Katy Ereira - #PHPDay24 - @[email protected] Shrek, onions and architecture Katy Ereira

Slide 2

Slide 2 text

Katy Ereira - #PHPDay24 - @[email protected]

Slide 3

Slide 3 text

Katy Ereira - #PHPDay24 - @[email protected]

Slide 4

Slide 4 text

Katy Ereira - #PHPDay24 - @[email protected]

Slide 5

Slide 5 text

Katy Ereira - #PHPDay24 - @[email protected]

Slide 6

Slide 6 text

Katy Ereira - #PHPDay24 - @[email protected] 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

Slide 7

Slide 7 text

Katy Ereira - #PHPDay24 - @[email protected] 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.

Slide 8

Slide 8 text

Katy Ereira - #PHPDay24 - @[email protected] The layers of an onion can be separated one at a time, from the outside in. 00 Hexagonal architecture and dependency inversion

Slide 9

Slide 9 text

Katy Ereira - #PHPDay24 - @[email protected] 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

Slide 10

Slide 10 text

Katy Ereira - #PHPDay24 - @[email protected] 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.

Slide 11

Slide 11 text

Katy Ereira - #PHPDay24 - @[email protected] ➔ HTTP routes ➔ REST API entry points ➔ GraphQL schemas ➔ HTML templates / views Flowers and leaves presentation

Slide 12

Slide 12 text

Katy Ereira - #PHPDay24 - @[email protected] 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.

Slide 13

Slide 13 text

Katy Ereira - #PHPDay24 - @[email protected] ➔ Controllers ➔ Commands ➔ Service providers Skin application layer

Slide 14

Slide 14 text

Katy Ereira - #PHPDay24 - @[email protected] 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.

Slide 15

Slide 15 text

Katy Ereira - #PHPDay24 - @[email protected] ➔ Domain services ➔ Helpers ➔ Processes / algorithms Fleshy layers business logic

Slide 16

Slide 16 text

Katy Ereira - #PHPDay24 - @[email protected] 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.

Slide 17

Slide 17 text

Katy Ereira - #PHPDay24 - @[email protected] ➔ Entities ➔ Aggregates ➔ Value objects Base core domain model

Slide 18

Slide 18 text

Katy Ereira - #PHPDay24 - @[email protected] 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.

Slide 19

Slide 19 text

Katy Ereira - #PHPDay24 - @[email protected] ➔ SDKs ➔ Databases & ORMs ➔ Frameworks ➔ Adaptors Roots infrastructure

Slide 20

Slide 20 text

Katy Ereira - #PHPDay24 - @[email protected]

Slide 21

Slide 21 text

Katy Ereira - #PHPDay24 - @[email protected] Onions grow from the basal stem whose cells contain the information for growth. 01 Domain driven design.

Slide 22

Slide 22 text

Katy Ereira - #PHPDay24 - @[email protected] 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!

Slide 23

Slide 23 text

Katy Ereira - #PHPDay24 - @[email protected] 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!

Slide 24

Slide 24 text

Katy Ereira - #PHPDay24 - @[email protected] 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!

Slide 25

Slide 25 text

Katy Ereira - #PHPDay24 - @[email protected] 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!

Slide 26

Slide 26 text

Katy Ereira - #PHPDay24 - @[email protected] 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!

Slide 27

Slide 27 text

Katy Ereira - #PHPDay24 - @[email protected] 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!

Slide 28

Slide 28 text

Katy Ereira - #PHPDay24 - @[email protected]

Slide 29

Slide 29 text

Katy Ereira - #PHPDay24 - @[email protected] One does not simply bite into an onion. One must master the elements of good cooking. 02 Coding without tears.

Slide 30

Slide 30 text

Katy Ereira - #PHPDay24 - @[email protected]

Slide 31

Slide 31 text

Katy Ereira - #PHPDay24 - @[email protected] 🧂 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?”

Slide 32

Slide 32 text

Katy Ereira - #PHPDay24 - @[email protected] /** * @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;

Slide 33

Slide 33 text

Katy Ereira - #PHPDay24 - @[email protected] 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; }

Slide 34

Slide 34 text

Katy Ereira - #PHPDay24 - @[email protected] 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;

Slide 35

Slide 35 text

Katy Ereira - #PHPDay24 - @[email protected] 🧈 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.”

Slide 36

Slide 36 text

Katy Ereira - #PHPDay24 - @[email protected] 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); }

Slide 37

Slide 37 text

Katy Ereira - #PHPDay24 - @[email protected] 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, ); }

Slide 38

Slide 38 text

Katy Ereira - #PHPDay24 - @[email protected] 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; }

Slide 39

Slide 39 text

Katy Ereira - #PHPDay24 - @[email protected] 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; }

Slide 40

Slide 40 text

Katy Ereira - #PHPDay24 - @[email protected] 🍶 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.”

Slide 41

Slide 41 text

Katy Ereira - #PHPDay24 - @[email protected] 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 {}

Slide 42

Slide 42 text

Katy Ereira - #PHPDay24 - @[email protected] 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’, ), ); } }

Slide 43

Slide 43 text

Katy Ereira - #PHPDay24 - @[email protected] 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’, ); } }

Slide 44

Slide 44 text

Katy Ereira - #PHPDay24 - @[email protected] Protip: Deptrac A static analysis tool

Slide 45

Slide 45 text

Katy Ereira - #PHPDay24 - @[email protected] 🔥 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”

Slide 46

Slide 46 text

Katy Ereira - #PHPDay24 - @[email protected] Testing Legacy Code - a talk. A shameless plug…

Slide 47

Slide 47 text

Katy Ereira - #PHPDay24 - @[email protected] 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; }

Slide 48

Slide 48 text

Katy Ereira - #PHPDay24 - @[email protected] 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; }

Slide 49

Slide 49 text

Katy Ereira - #PHPDay24 - @[email protected]

Slide 50

Slide 50 text

Katy Ereira - #PHPDay24 - @[email protected] 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 @[email protected] joind.in/talk/1bb9b Clean Coding Applications taste better when you master the elements of good coding. ✔ Delete redundant comments. Refactor to use DI.