2
Share docs with
other systems
3
Which protocol?
4
5
1 month later…
6
7
Done!
8
1 month later…
9
10
Done!
11
+
12
The secrets of
13
Hexagonal

Architecture
Separate Domain
& Infrastructure
14
Maintainable Software
=
Domain 

vs
Infrastructure
Twitter 

GitHub 

Medium
16
}@nicoespeon
Nicolas Carlo
17
80 countries
18
Seat
Stop
Departure
Roundtrip
Leg
Taxes
Discount code
Fees
Domain is our
business
19
We could do our
business without
software!
20
21
Infrastructure is
how we make it work
22
Infrastructure 

is a detail
23
Put the Domain
at the heart of
the software
24
Problems mixing
Domain & Infra.
Get a poem from a
poetry library, or return
the default one.
26
27
class PoetryReader {
giveMeSomePoetry(): string {
}
}
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" });
}
}
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;
}
}
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;
}
}
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
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
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;
}
}
const pathToPoem = path.join(!__dirname, "poem.txt");
let poem = fs.readFileSync(pathToPoem, { encoding: "utf8" });
Hard to 

see
the Business
34
const pathToPoem = path.join(!__dirname, "poem.txt");
let poem = fs.readFileSync(pathToPoem, { encoding: "utf8" });
Hard to 

test
the Business
35
const pathToPoem = path.join(!__dirname, "poem.txt");
let poem = fs.readFileSync(pathToPoem, { encoding: "utf8" });
Hard to 

change
the Business
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;
}
}
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
Hexagonal
Architecture
The simplest way to
40
Separate Domain 

& Infrastructure
41
Infra.
Domain
42
Infra.
Domain
dependency
43
Infra.
Domain
dependency
44
Interface
Adapter
use
implement
45
Port
Adapter
46
Port
Adapter
Ports & Adapters architecture
47
Business
language

only
48
Left-side Right-side
49
Left-side Right-side
Adapter
Adapter
50
Left-side Right-side
Adapter
Adapter
Adapter
Adapter
A concrete
example
class PoetryReader {
}
Domain
52
class PoetryReader {
poetryLibrary: ObtainPoems;
constructor(poetryLibrary!?: ObtainPoems) {
this.poetryLibrary = poetryLibrary;
}
}
Domain
53
class PoetryReader {
poetryLibrary: ObtainPoems;
constructor(poetryLibrary!?: ObtainPoems) {
this.poetryLibrary = poetryLibrary;
}
}
Domain
54
Dependency
Injection
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
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
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
Domain
type Poem = string;
interface ObtainPoems {
getAPoem(): Poem
}
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
!// 1. Instantiate the right-side adapter(s)
!// 2. Instantiate the hexagon (domain)
!// 3. Instantiate the left-side adapter(s)
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
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
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
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
const poetryReader = new PoetryReader();
65
const poetryReader = new PoetryReader(poetryLibrary);
66
Pros and cons
This ain't new
68
"Program to an Interface" − OOP
"Isolate side effects" − FP
The Onion Architecture
Plug different adapters
to the Domain
69
A good architect
defers decisions
70
Start with
something simple
71
But… it's only
the first step!
72
73
More layers
74
Ubiquitous Language 

Bounded Contexts

Domain modelling
Separate Domain
& Infrastructure
75
🙏
Twitter 

GitHub 

Medium
}@nicoespeon
Nicolas Carlo
@swcraftmontreal
Software Crafters Montréal
Every 1st Tuesday of the month.
Bonuses
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
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

The Secrets of Hexagonal Architecture

  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
    We could doour business without software! 20
  • 21.
  • 22.
    Infrastructure is how wemake it work 22
  • 23.
  • 24.
    Put the Domain atthe heart of the software 24
  • 25.
  • 26.
    Get a poemfrom a poetry library, or return the default one. 26
  • 27.
  • 28.
    28 import * asfs 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" }); } }
  • 29.
    29 import * asfs 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 import * asfs 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; } }
  • 31.
    31 import * asfs 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
  • 32.
    32 import * asfs 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
  • 33.
    33 import * asfs 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; } }
  • 34.
    const pathToPoem =path.join(!__dirname, "poem.txt"); let poem = fs.readFileSync(pathToPoem, { encoding: "utf8" }); Hard to 
 see the Business 34
  • 35.
    const pathToPoem =path.join(!__dirname, "poem.txt"); let poem = fs.readFileSync(pathToPoem, { encoding: "utf8" }); Hard to 
 test the Business 35
  • 36.
    const pathToPoem =path.join(!__dirname, "poem.txt"); let poem = fs.readFileSync(pathToPoem, { encoding: "utf8" }); Hard to 
 change the Business 36
  • 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; } }
  • 38.
    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
  • 39.
  • 40.
    The simplest wayto 40 Separate Domain 
 & Infrastructure
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
    class PoetryReader { poetryLibrary:ObtainPoems; constructor(poetryLibrary!?: ObtainPoems) { this.poetryLibrary = poetryLibrary; } } Domain 53
  • 54.
    class PoetryReader { poetryLibrary:ObtainPoems; constructor(poetryLibrary!?: ObtainPoems) { this.poetryLibrary = poetryLibrary; } } Domain 54 Dependency Injection
  • 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 55
  • 56.
    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
  • 57.
    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
  • 58.
    Domain type Poem =string; interface ObtainPoems { getAPoem(): Poem } 58
  • 59.
    import * asfs 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
  • 60.
    !// 1. Instantiatethe right-side adapter(s) !// 2. Instantiate the hexagon (domain) !// 3. Instantiate the left-side adapter(s) 60
  • 61.
    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
  • 62.
    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
  • 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); 63
  • 64.
    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
  • 65.
    const poetryReader =new PoetryReader(); 65
  • 66.
    const poetryReader =new PoetryReader(poetryLibrary); 66
  • 67.
  • 68.
    This ain't new 68 "Programto an Interface" − OOP "Isolate side effects" − FP The Onion Architecture
  • 69.
  • 70.
  • 71.
  • 72.
    But… it's only thefirst step! 72
  • 73.
  • 74.
    74 Ubiquitous Language 
 BoundedContexts
 Domain modelling
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
    test("give verses whenasked 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
  • 80.
    test("give verses froma 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