The use of ORMs and class-based
validation frameworks ruins your
projects.
Slide 3
Slide 3 text
The use of ORMs and class-based
validation frameworks ruins your
projects.
unre
f
lected
Slide 4
Slide 4 text
Not here to tell you to stop
using your favorite tool.
Slide 5
Slide 5 text
“Until you make the unconscious
conscious, it will direct your life and you
will call it fate.”
― C.G. Jung
Slide 6
Slide 6 text
Why do we
write software?
Slide 7
Slide 7 text
Business
logic
Slide 8
Slide 8 text
if form.valid():
user = db_session.execute(
select(User).where(User.id == form.user_id)
)
if user.something and form.something_else:
ruin_life(user)
db_session.commit()
Slide 9
Slide 9 text
Therac-25
Slide 10
Slide 10 text
British Post
O
ff
ice scandal
Slide 11
Slide 11 text
Design
pressure
Slide 12
Slide 12 text
Two pieces of code are
coupled if they can only
be understood by looking
at both.
[T] [Q] [S]
[R] [M] [L] [V] [G]
[D] [V] [V] [Q] [N] [C]
[H] [T] [S] [C] [V] [D] [Z]
[Q] [J] [D] [M] [Z] [C] [M] [F]
[N] [B] [H] [N] [B] [W] [N] [J] [M]
[P] [G] [R] [Z] [Z] [C] [Z] [G] [P]
[B] [W] [N] [P] [D] [V] [G] [L] [T]
-----------------------------------
1 2 3 4 5 6 7 8 9
Harbor
move 5 from 4 to 9
move 3 from 5 to 1
move 12 from 9 to 6
...
Instructions
“You should structure
your data so that the
problem solves itself.”
Slide 21
Slide 21 text
@dataclass
class Instruction:
source: int
destination: int
count: int
Slide 22
Slide 22 text
@dataclass
class Instruction:
source: int
destination: int
count: int
@dataclass
class Harbor:
stacks: List[Stack]
def execute(self, i: Instruction):
source = self.stacks[i.source]
destination = self.stacks[i.destination]
payload = source.popsome(i.count)
destination.extend(payload)
Slide 23
Slide 23 text
@dataclass
class Instruction:
source: int
destination: int
count: int
@dataclass
class Harbor:
stacks: List[Stack]
def execute(self, i: Instruction):
source = self.stacks[i.source]
destination = self.stacks[i.destination]
payload = source.popsome(i.count)
destination.extend(payload)
Slide 24
Slide 24 text
@dataclass
class Instruction:
source: int
destination: int
count: int
@dataclass
class Harbor:
stacks: List[Stack]
def execute(self, i: Instruction):
source = self.stacks[i.source]
destination = self.stacks[i.destination]
payload = source.popsome(i.count)
destination.extend(payload)
Slide 25
Slide 25 text
@dataclass
class Instruction:
source: int
destination: int
count: int
@dataclass
class Harbor:
stacks: List[Stack]
def execute(self, i: Instruction):
source = self.stacks[i.source]
destination = self.stacks[i.destination]
payload = source.popsome(i.count)
destination.extend(payload)
@dataclass
class Stack:
boxes: List[Box]
def popsome(self, count: int) -> List[Box]:
tail = self.boxes[-count:]
self.boxes = self.boxes[:-count]
return tail
def extend(self, some: List[Box]):
self.boxes.extend(some)
Slide 26
Slide 26 text
@dataclass
class Instruction:
source: int
destination: int
count: int
@dataclass
class Harbor:
stacks: List[Stack]
def execute(self, i: Instruction):
source = self.stacks[i.source]
destination = self.stacks[i.destination]
payload = source.popsome(i.count)
destination.extend(payload)
@dataclass
class Stack:
boxes: List[Box]
def popsome(self, count: int) -> List[Box]:
tail = self.boxes[-count:]
self.boxes = self.boxes[:-count]
return tail
def extend(self, some: List[Box]):
self.boxes.extend(some)
Slide 27
Slide 27 text
@dataclass
class Instruction:
source: int
destination: int
count: int
@dataclass
class Harbor:
stacks: List[Stack]
def execute(self, i: Instruction):
source = self.stacks[i.source]
destination = self.stacks[i.destination]
payload = source.popsome(i.count)
destination.extend(payload)
@dataclass
class Stack:
boxes: List[Box]
def popsome(self, count: int) -> List[Box]:
tail = self.boxes[-count:]
self.boxes = self.boxes[:-count]
return tail
def extend(self, some: List[Box]):
self.boxes.extend(some)
Slide 28
Slide 28 text
@dataclass
class Instruction:
source: int
destination: int
count: int
@dataclass
class Harbor:
stacks: List[Stack]
def execute(self, i: Instruction):
source = self.stacks[i.source]
destination = self.stacks[i.destination]
payload = source.popsome(i.count)
destination.extend(payload)
@dataclass
class Stack:
boxes: List[Box]
def popsome(self, count: int) -> List[Box]:
tail = self.boxes[-count:]
self.boxes = self.boxes[:-count]
return tail
def extend(self, some: List[Box]):
self.boxes.extend(some)
Slide 29
Slide 29 text
“In software engineering, a
domain model is a conceptual
model of the domain that
incorporates both behavior
and data.”
— Wikipedia
Domain Model
Slide 30
Slide 30 text
@dataclass
class Instruction:
source: int
destination: int
count: int
@dataclass
class Harbor:
stacks: List[Stack]
def execute(self, i: Instruction):
source = self.stacks[i.source]
destination = self.stacks[i.destination]
payload = source.popsome(i.count)
destination.extend(payload)
class Stack:
boxes: List[Box]
def popsome(self, count: int) -> List[Box]:
tail = self.boxes[-count:]
self.boxes = self.boxes[:-count]
return tail
def extend(self, some: List[Box]):
self.boxes.extend(some)
Slide 31
Slide 31 text
@dataclass
class Instruction:
source: int
destination: int
count: int
@dataclass
class Harbor:
stacks: List[Stack]
def execute(self, i: Instruction):
source = self.stacks[i.source]
destination = self.stacks[i.destination]
payload = source.popsome(i.count)
destination.extend(payload)
class Stack:
boxes: List[Box]
def popsome(self, count: int) -> List[Box]:
tail = self.boxes[-count:]
self.boxes = self.boxes[:-count]
return tail
def extend(self, some: List[Box]):
self.boxes.extend(some)
Slide 32
Slide 32 text
The Darkness
def parse_harbor(stackstr: Iterable[str]) -> Harbor:
bottomup = list(stackitr)[:-1]
harbor: Harbor = Harbor([[] for _ in range(N_STACKS)])
for line in bottomup:
tokens = [
''.join(x for _, x in g[1])
for g in groupby(
enumerate(line),
lambda t: t[0] // N_CHARS_IN_BOX_TOKEN
)
]
boxes: List[Box] = [t.strip('[] ') for t in tokens]
for box, stack in zip(boxes, harbor.iter_stacks()):
if box: stack.append(box)
return harbor
Slide 33
Slide 33 text
“The shitty stu
ff
should happen on the outside layer
of your program, that interacts with the "outside
world". Once it's inside, organize it into nice data
structures so that operations (i.e. code) are as self
evident as possible.”
— Matthew Drury
Slide 34
Slide 34 text
The shape of the data
determines
the shape of the code
Slide 35
Slide 35 text
Problem #1
best model for web apis
≠ best model for business logic
≠ best model for databases
Slide 36
Slide 36 text
Con
f
licting goals
Web API
what is best for the user?
what is a good standard?
(e.g., JSON:API)
Slide 37
Slide 37 text
Con
f
licting goals
Web API
what is best for the user?
what is a good standard?
(e.g., JSON:API)
Database schema
how to store data e
ff
ectively?
how to normalize?
→ storage vs performance
Slide 38
Slide 38 text
Con
f
licting goals
Web API
what is best for the user?
what is a good standard?
(e.g., JSON:API)
Database schema
how to store data e
ff
ectively?
how to normalize?
→ storage vs performance
Domain model
how to solve
business requirement?
Slide 39
Slide 39 text
Con
f
licting goals
Web API
what is best for the user?
what is a good standard?
(e.g., JSON:API)
Database schema
how to store data e
ff
ectively?
how to normalize?
→ storage vs performance
Domain model
how to solve
business requirement?
Slide 40
Slide 40 text
Con
f
licting goals
Web API
what is best for the user?
what is a good standard?
(e.g., JSON:API)
Database schema
how to store data e
ff
ectively?
how to normalize?
→ storage vs performance
Domain model
how to solve
business requirement?
Slide 41
Slide 41 text
No content
Slide 42
Slide 42 text
No content
Slide 43
Slide 43 text
type CustomerID = UUID
Slide 44
Slide 44 text
type CustomerID = UUID
class OpenInvoice:
customer: CustomerID
amount: Decimal
due_on: date
Slide 45
Slide 45 text
type CustomerID = UUID
class OpenInvoice:
customer: CustomerID
amount: Decimal
due_on: date
class PaidInvoice:
customer: CustomerID
amount: Decimal
paid_on: date
Slide 46
Slide 46 text
type CustomerID = UUID
class OpenInvoice:
customer: CustomerID
amount: Decimal
due_on: date
class PaidInvoice:
customer: CustomerID
amount: Decimal
paid_on: date
class OverdueInvoice:
customer: CustomerID
amount: Decimal
overdue_by: int
reminder_level: int
Slide 47
Slide 47 text
type CustomerID = UUID
class OpenInvoice:
customer: CustomerID
amount: Decimal
due_on: date
class PaidInvoice:
customer: CustomerID
amount: Decimal
paid_on: date
class OverdueInvoice:
customer: CustomerID
amount: Decimal
overdue_by: int
reminder_level: int
def release_the_hounds(
invs: list[OverdueInvoice]
) -> list[CustomerID]:
...
Slide 48
Slide 48 text
Approach 1 (bad)
ORM xor Web = primary
Slide 49
Slide 49 text
Approach 1 (bad)
ORM xor Web = primary
database-driven
“design”
Slide 50
Slide 50 text
No content
Slide 51
Slide 51 text
Approach 2 (worse)
🤝
Slide 52
Slide 52 text
Approach 2 (worse)
domain model 🥺
pressure
pressure
🤝
Slide 53
Slide 53 text
Problem #2
Both calcify
(eventually)
Slide 54
Slide 54 text
Problem #2
Both calcify
(eventually)
Slide 55
Slide 55 text
You have lost control
over your domain model
and therefore over your
business logic.
Slide 56
Slide 56 text
”It's ok to have duplicative-looking types for di
ff
erent
layers of a project—e.g. ORM, API schema, and
business logic. Types need to be easy to change and
reason about so we can adapt them to our evolving
requirements. Types near the edge (like API schemas
and database tables) are inherently less
f
lexible. It's
often better to explicitly map types between these
layers rather than create a "franken-type" and allow
the least
f
lexible layer to calcify the rest.”
— Adam Montgomery
Slide 57
Slide 57 text
”good examples are by far the
hardest part of explaining design”
— Kent Beck
Start with the
domain model
then start ~engineering~
It depends, but
Slide 66
Slide 66 text
No content
Slide 67
Slide 67 text
No content
Slide 68
Slide 68 text
“To me, complexity is not about how many keys I
have to press – it’s about how di
ff
icult it is to reason
about the consequences of what I’m doing.”
— me
We’re too lazy to type.
Slide 69
Slide 69 text
Takeaways
Slide 70
Slide 70 text
Takeaways
1. your business code is sacred
Slide 71
Slide 71 text
Takeaways
1. your business code is sacred
2. protect it from your tools
Slide 72
Slide 72 text
Takeaways
1. your business code is sacred
2. protect it from your tools
3. write tests; get a better design