Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

The Secrets of Hexagonal Architecture

275 views

Published on

Slides of the talk I gave at ConFoo Montréal 2019 about "Hexagonal Architecture".

Hexagonal Architecture, Clean Architecture, Domain-Driven Design… You may have heard about them. Let's start from scratch in this session and go beyond the buzzwords. We'll go together though these ideas to understand how you can improve the maintainability of your projects', either greenfield or legacy.

Published in: Technology
  • Be the first to comment

  • Be the first to like this

The Secrets of Hexagonal Architecture

  1. 1. 2
  2. 2. Share docs with other systems 3
  3. 3. Which protocol? 4
  4. 4. 5
  5. 5. 1 month later… 6
  6. 6. 7
  7. 7. Done! 8
  8. 8. 1 month later… 9
  9. 9. 10
  10. 10. Done! 11
  11. 11. + 12
  12. 12. The secrets of 13 Hexagonal
 Architecture
  13. 13. Separate Domain & Infrastructure 14 Maintainable Software =
  14. 14. Domain 
 vs Infrastructure
  15. 15. Twitter 
 GitHub 
 Medium 16 }@nicoespeon Nicolas Carlo
  16. 16. 17 80 countries
  17. 17. 18 Seat Stop Departure Roundtrip Leg Taxes Discount code Fees
  18. 18. Domain is our business 19
  19. 19. We could do our business without software! 20
  20. 20. 21
  21. 21. Infrastructure is how we make it work 22
  22. 22. Infrastructure 
 is a detail 23
  23. 23. Put the Domain at the heart of the software 24
  24. 24. Problems mixing Domain & Infra.
  25. 25. Get a poem from a poetry library, or return the default one. 26
  26. 26. 27 class PoetryReader { giveMeSomePoetry(): string { } }
  27. 27. 28 import * as fs from "fs"; import * as path from "path"; class PoetryReader { giveMeSomePoetry(): string { const pathToPoem = path.join(!__dirname, "poem.txt"); let poem = fs.readFileSync(pathToPoem, { encoding: "utf8" }); } }
  28. 28. 29 import * as fs from "fs"; import * as path from "path"; class PoetryReader { giveMeSomePoetry(): string { const pathToPoem = path.join(!__dirname, "poem.txt"); let poem = fs.readFileSync(pathToPoem, { encoding: "utf8" }); if (!poem) { poem = "If you could read a leaf or treernyou’d have no need of books. rn!-- © Alistair Cockburn (1987)"; } return poem; } }
  29. 29. 30 import * as fs from "fs"; import * as path from "path"; class PoetryReader { giveMeSomePoetry(): string { const pathToPoem = path.join(!__dirname, "poem.txt"); let poem = fs.readFileSync(pathToPoem, { encoding: "utf8" }); if (!poem) { poem = "If you could read a leaf or treernyou’d have no need of books. rn!-- © Alistair Cockburn (1987)"; } return poem; } }
  30. 30. 31 import * as fs from "fs"; import * as path from "path"; class PoetryReader { giveMeSomePoetry(): string { const pathToPoem = path.join(!__dirname, "poem.txt"); let poem = fs.readFileSync(pathToPoem, { encoding: "utf8" }); if (!poem) { poem = "If you could read a leaf or treernyou’d have no need of books. rn!-- © Alistair Cockburn (1987)"; } return poem; } } Domain
  31. 31. 32 import * as fs from "fs"; import * as path from "path"; class PoetryReader { giveMeSomePoetry(): string { const pathToPoem = path.join(!__dirname, "poem.txt"); let poem = fs.readFileSync(pathToPoem, { encoding: "utf8" }); if (!poem) { poem = "If you could read a leaf or treernyou’d have no need of books. rn!-- © Alistair Cockburn (1987)"; } return poem; } } Infrastructure
  32. 32. 33 import * as fs from "fs"; import * as path from "path"; class PoetryReader { giveMeSomePoetry(): string { const pathToPoem = path.join(!__dirname, "poem.txt"); let poem = fs.readFileSync(pathToPoem, { encoding: "utf8" }); if (!poem) { poem = "If you could read a leaf or treernyou’d have no need of books. rn!-- © Alistair Cockburn (1987)"; } return poem; } }
  33. 33. const pathToPoem = path.join(!__dirname, "poem.txt"); let poem = fs.readFileSync(pathToPoem, { encoding: "utf8" }); Hard to 
 see the Business 34
  34. 34. const pathToPoem = path.join(!__dirname, "poem.txt"); let poem = fs.readFileSync(pathToPoem, { encoding: "utf8" }); Hard to 
 test the Business 35
  35. 35. const pathToPoem = path.join(!__dirname, "poem.txt"); let poem = fs.readFileSync(pathToPoem, { encoding: "utf8" }); Hard to 
 change the Business 36
  36. 36. 37 import FileReader from "lib/file-reader"; class PoetryReader { giveMeSomePoetry(): string { let poem = FileReader.read("poem.txt"); if (!poem) { poem = "If you could read a leaf or treernyou’d have no need of books. rn!-- © Alistair Cockburn (1987)"; } return poem; } }
  37. 37. import FileReader from "lib/file-reader"; class PoetryReader { giveMeSomePoetry(): string { let poem = FileReader.read("poem.txt"); if (!poem) { poem = "If you could read a leaf or treernyou’d have no need of books. rn!-- © Alistair Cockburn (1987)"; } return poem; } } Same 
 problem 38
  38. 38. Hexagonal Architecture
  39. 39. The simplest way to 40 Separate Domain 
 & Infrastructure
  40. 40. 41 Infra. Domain
  41. 41. 42 Infra. Domain dependency
  42. 42. 43 Infra. Domain dependency
  43. 43. 44 Interface Adapter use implement
  44. 44. 45 Port Adapter
  45. 45. 46 Port Adapter Ports & Adapters architecture
  46. 46. 47 Business language
 only
  47. 47. 48 Left-side Right-side
  48. 48. 49 Left-side Right-side Adapter Adapter
  49. 49. 50 Left-side Right-side Adapter Adapter Adapter Adapter
  50. 50. A concrete example
  51. 51. class PoetryReader { } Domain 52
  52. 52. class PoetryReader { poetryLibrary: ObtainPoems; constructor(poetryLibrary!?: ObtainPoems) { this.poetryLibrary = poetryLibrary; } } Domain 53
  53. 53. class PoetryReader { poetryLibrary: ObtainPoems; constructor(poetryLibrary!?: ObtainPoems) { this.poetryLibrary = poetryLibrary; } } Domain 54 Dependency Injection
  54. 54. class PoetryReader { poetryLibrary: ObtainPoems; constructor(poetryLibrary!?: ObtainPoems) { this.poetryLibrary = poetryLibrary; } giveMeSomePoetry(): Poem { if (!this.poetryLibrary) { return "If you could read a leaf or treernyou’d have no need of books. rn!-- © Alistair Cockburn (1987)"; } return this.poetryLibrary.getAPoem(); } } Domain 55
  55. 55. class PoetryReader { poetryLibrary: ObtainPoems; constructor(poetryLibrary!?: ObtainPoems) { this.poetryLibrary = poetryLibrary; } giveMeSomePoetry(): Poem { if (!this.poetryLibrary) { return "If you could read a leaf or treernyou’d have no need of books. rn!-- © Alistair Cockburn (1987)"; } return this.poetryLibrary.getAPoem(); } } Domain 56
  56. 56. giveMeSomePoetry(): Poem { if (!this.poetryLibrary) { return "If you could read a leaf or treernyou’d have no need of books.rn!-- © Alistair Cockburn (1987)"; } return this.poetryLibrary.getAPoem(); } 57 giveMeSomePoetry(): string { let poem = FileReader.read("poem.txt"); if (!poem) { poem = "If you could read a leaf or treernyou’d have no need of books.rn!-- © Alistair Cockburn (1987)"; } return poem; } Implementation Intention
  57. 57. Domain type Poem = string; interface ObtainPoems { getAPoem(): Poem } 58
  58. 58. import * as fs from "fs"; import * as path from "path"; import ObtainPoems from "!../domain/obtain-poems"; class ObtainPoemsFromFS implements ObtainPoems { getAPoem() { const pathToPoem = path.join(!__dirname, "poem.txt"); const poem = fs.readFileSync(pathToPoem, { encoding: "utf8" }); return poem; } } Infra. 59
  59. 59. !// 1. Instantiate the right-side adapter(s) !// 2. Instantiate the hexagon (domain) !// 3. Instantiate the left-side adapter(s) 60
  60. 60. import ObtainPoemsFromFS from "./infrastructure/obtain-poems-from-fs"; !// 1. Instantiate the right-side adapter(s) const poetryLibrary = new ObtainPoemsFromFS(); !// 2. Instantiate the hexagon (domain) !// 3. Instantiate the left-side adapter(s) 61
  61. 61. import PoetryReader from "./domain/poetry-reader"; import ObtainPoemsFromFS from "./infrastructure/obtain-poems-from-fs"; !// 1. Instantiate the right-side adapter(s) const poetryLibrary = new ObtainPoemsFromFS(); !// 2. Instantiate the hexagon (domain) const poetryReader = new PoetryReader(poetryLibrary); !// 3. Instantiate the left-side adapter(s) 62
  62. 62. import PoetryReader from "./domain/poetry-reader"; import ObtainPoemsFromFS from "./infrastructure/obtain-poems-from-fs"; import ConsoleApi from "./infrastructure/console-api"; !// 1. Instantiate the right-side adapter(s) const poetryLibrary = new ObtainPoemsFromFS(); !// 2. Instantiate the hexagon (domain) const poetryReader = new PoetryReader(poetryLibrary); !// 3. Instantiate the left-side adapter(s) const consoleApi = new ConsoleApi(poetryReader); 63
  63. 63. import PoetryReader from "./domain/poetry-reader"; import ObtainPoemsFromFS from "./infrastructure/obtain-poems-from-fs"; import ConsoleApi from "./infrastructure/console-api"; !// 1. Instantiate the right-side adapter(s) const poetryLibrary = new ObtainPoemsFromFS(); !// 2. Instantiate the hexagon (domain) const poetryReader = new PoetryReader(poetryLibrary); !// 3. Instantiate the left-side adapter(s) const consoleApi = new ConsoleApi(poetryReader); !// App logic is only using left-side adapter(s). console.log("Here is some poetry:n"); consoleApi.ask(); 64
  64. 64. const poetryReader = new PoetryReader(); 65
  65. 65. const poetryReader = new PoetryReader(poetryLibrary); 66
  66. 66. Pros and cons
  67. 67. This ain't new 68 "Program to an Interface" − OOP "Isolate side effects" − FP The Onion Architecture
  68. 68. Plug different adapters to the Domain 69
  69. 69. A good architect defers decisions 70
  70. 70. Start with something simple 71
  71. 71. But… it's only the first step! 72
  72. 72. 73 More layers
  73. 73. 74 Ubiquitous Language 
 Bounded Contexts
 Domain modelling
  74. 74. Separate Domain & Infrastructure 75 🙏
  75. 75. Twitter 
 GitHub 
 Medium }@nicoespeon Nicolas Carlo
  76. 76. @swcraftmontreal Software Crafters Montréal Every 1st Tuesday of the month.
  77. 77. Bonuses
  78. 78. test("give verses when asked for poetry", () !=> { const poetryReader = new PoetryReader(); const verses = poetryReader.giveMeSomePoetry(); expect(verses).toEqual( "If you could read a leaf or treernyou’d have no need of books.rn!-- © Alistair Cockburn (1987)" ); }); Tests
  79. 79. test("give verses from a PoetryLibrary", () !=> { const poetryLibrary: ObtainPoems = { getAPoem() { return "I want to sleep…rn!-- Masaoka Shiki (1867-1902)"; } }; const poetryReader = new PoetryReader(poetryLibrary); const verses = poetryReader.giveMeSomePoetry(); expect(verses).toEqual( "I want to sleep…rn!-- Masaoka Shiki (1867-1902)" ); }); Tests

×