Domain Driven Rails

105
@skwp @reverbdotcom #wcr14 Yan Pritzker CTO, Reverb.com @skwp @reverbdotcom Domain Driven Rails https://speakerdeck.com/skwp

Transcript of Domain Driven Rails

@skwp @reverbdotcom #wcr14

Yan PritzkerCTO, Reverb.com

@skwp @reverbdotcom

Domain Driven Rails

https://speakerdeck.com/skwp

@skwp @reverbdotcom #wcr14

Are you happy with the SIZE and COMPLEXITY

of your models?

@skwp @reverbdotcom #wcr14

Are you happy with the SCALABILITY of your team?

@skwp @reverbdotcom #wcr14

Are you happy with the ADAPTABILITY

of your business?

@skwp @reverbdotcom #wcr14

What are you building?

@skwp @reverbdotcom #wcr14

Rails is a detail!"

Decouple all things"

Is the code better?"

Keep it Railsy"

@skwp @reverbdotcom #wcr14

Somewhere in between

Simple CRUD"Apps"

Complex Enterprise

Logic"

@skwp @reverbdotcom #wcr14

I want to discover relevant compromises

rather than defend ideals

@skwp @reverbdotcom #wcr14

@skwp @reverbdotcom #wcr14

Over 115

Model Classes

@skwp @reverbdotcom #wcr14

Over 1000 Total

Classes

@skwp @reverbdotcom #wcr14

@skwp @reverbdotcom #wcr14

Not great, but we have only three chubby models

@skwp @reverbdotcom #wcr14

Are monoliths bad for business?

@skwp @reverbdotcom #wcr14

Quick IterationsLow Operational

ComplexityRefactoring

Monolith

ServiceService

Service Service

Service

Early 2013 - Startup / Proof of Concept

Monolith

ServiceService

Service Service

Service

2014 - Growth Phase

@skwp @reverbdotcom #wcr14

http://martinfowler.com/articles/distributed-objects-microservices.html

“I'm wary of distribution and my default inclination is to prefer

a monolithic design”

“While small microservices are certainly simpler to reason about, I worry that this pushes complexity into the

interconnections between services”

“Refactoring becomes much harder when you have to do it across remote boundaries.”

@skwp @reverbdotcom #wcr14

Quick IterationsLow Operational

ComplexityMay lead to a BBOM

@skwp @reverbdotcom #wcr14

Maintainable Monoliths

Can Be Achieved

@skwp @reverbdotcom #wcr14

@skwp @reverbdotcom #wcr14

@skwp @reverbdotcom #wcr14

Product 400 LOC

~150 LOC non-ActiveRecord Churn: 49 changes this year"

Order 333 LOC

~200 LOC non-ActiveRecord Churn: 36 changes this year

User 338 LOC

~200 LOC non-ActiveRecord Churn: 29 changes this year

CHURN

Product

OrderUser

@skwp @reverbdotcom #wcr14

Commonly used classes are hard

to refactor

@skwp @reverbdotcom #wcr14

Stop modifying

code!(Open/Closed Principle)

@skwp @reverbdotcom #wcr14

Don’t put different rates

of change together"

Kent Beck - Smalltalk Best Practice Patterns(see also: Single Responsibility Principle)

@skwp @reverbdotcom #wcr14

Don’t put different rates

of change together"

Kent Beck - Smalltalk Best Practice Patterns

Data Model

Business Logic

@skwp @reverbdotcom #wcr14

Separate what the system is "

from what the system does"

James Copelien & Trygve Reenskaug(Data, Context, Interaction)

@skwp @reverbdotcom #wcr14

Where does business logic go?

@skwp @reverbdotcom #wcr14

Controller

2005

ActiveRecord

Mailers

Services

User

@skwp @reverbdotcom #wcr14

Console?"Rake task?"

Background jobs?"API layer?"Testing?

What about…

@skwp @reverbdotcom #wcr14

Order

Fat models?

@skwp @reverbdotcom #wcr14

OrderRefund

@skwp @reverbdotcom #wcr14

OrderRefund

Ship

@skwp @reverbdotcom #wcr14

OrderRefund

ShipCheck Fraud Risk

@skwp @reverbdotcom #wcr14

“Active Record is a good choice for domain logic that isn't too

complex, such as creates, reads, updates, and deletes”

Martin Fowler

@skwp @reverbdotcom #wcr14

If Controller and Model are all you have then

one has to be skinny and one has to be fat

@skwp @reverbdotcom #wcr14

Domain Layerskinny framework,

healthy business logic,"no fat anywhere

http://joncairns.com/2013/04/fat-model-skinny-controller-is-a-load-of-rubbish/

Model

Controller

View

The Rails WayThe

WayActiveRecord

Use Cases

Grape API

ControllersCron

Redis

Rake

HTTP Services

Workers

REntities

Roles

DB

ListenersEvents

@skwp @reverbdotcom #wcr14

app/reverbfor app specific

@skwp @reverbdotcom #wcr14

lib/reverbfor Open Source / Generic

@skwp @reverbdotcom #wcr14

Reverb::Namespace to avoid collisions with gems

@skwp @reverbdotcom #wcr14

@skwp @reverbdotcom #wcr14

This is amarketplace!

http://blog.8thlight.com/uncle-

bob/2011/09/30/Screaming-

Architecture.html

@skwp @reverbdotcom #wcr14

http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html

@skwp @reverbdotcom #wcr14

see also Hexagonal, Ports & Adapters, DCI

@skwp @reverbdotcom #wcr14

Clean Enough

Architecture"

@skwp @reverbdotcom #wcr14

Choosing the right fight

@skwp @reverbdotcom #wcr14

http://blog.8thlight.com/mike-ebert/2013/03/23/the-repository-pattern.html

@skwp @reverbdotcom #wcr14

http://blog.8thlight.com/mike-ebert/2013/03/23/the-repository-pattern.html

(but you should still read this)

@skwp @reverbdotcom #wcr14

User.where(…)User.activeUser.find(1)

This is easy to replace with something other than AR. Repository not required.

Don’t leak SQL outside of AR

@skwp @reverbdotcom #wcr14

Domain Logic in ActiveRecord and

Controllers leads to Churn

@skwp @reverbdotcom #wcr14

Use CasesReify complex business logic

into classes

@skwp @reverbdotcom #wcr14

@skwp @reverbdotcom #wcr14

This is not"Rails

@skwp @reverbdotcom #wcr14

@skwp @reverbdotcom #wcr14

Explicitly Require Dependency

http://myronmars.to/n/dev-blog/2012/12/5-reasons-to-avoid-bundler-require

Invoke It

@skwp @reverbdotcom #wcr14

Code Reuse!

Thin Shell

@skwp @reverbdotcom #wcr14

Stubby

Happy Path

@skwp @reverbdotcom #wcr14

Testing conditionals and side effects has nothing to do with Rails

@skwp @reverbdotcom #wcr14

NamingThe hardest problem in computer science

@skwp @reverbdotcom #wcr14

OrderService

@skwp @reverbdotcom #wcr14

OrderService

@skwp @reverbdotcom #wcr14

OrderService

NounService Grows Unbounded

@skwp @reverbdotcom #wcr14

Use verbs to narrow your scope

@skwp @reverbdotcom #wcr14

Order

@skwp @reverbdotcom #wcr14

Order

ShipOrder

@skwp @reverbdotcom #wcr14

Order

ShipOrder Cancel Order

@skwp @reverbdotcom #wcr14

Order

ShipOrder Cancel Order

RefundOrder

@skwp @reverbdotcom #wcr14

Order

ShipOrder Cancel Order

RefundOrder

Does notgrow over time

@skwp @reverbdotcom #wcr14

ShipOrder Cancel Order

RefundOrder

Don’t change"once you write them

@skwp @reverbdotcom #wcr14

Ubiquitous Language

@skwp @reverbdotcom #wcr14

@skwp @reverbdotcom #wcr14

What is this?

@skwp @reverbdotcom #wcr14

RolesAdd methods to objects on demand

in the context of a Use Case

@skwp @reverbdotcom #wcr14

Decorator

@skwp @reverbdotcom #wcr14

We added these methods

@skwp @reverbdotcom #wcr14

@skwp @reverbdotcom #wcr14

Methods related to each"other but loosely related"

to the parent concept"and used only in a few"

Use Cases

@skwp @reverbdotcom #wcr14

@skwp @reverbdotcom #wcr14

EventsAdd behavior with listeners

without modifying code

@skwp @reverbdotcom #wcr14

Order

ActiveRecord Callbacks

Send Email

@skwp @reverbdotcom #wcr14

Order

ActiveRecord Callbacks

Send Email

Call External Service

@skwp @reverbdotcom #wcr14

Order

ActiveRecord Callbacks

Send Email

Call External Service

ConditionalCallbacks

@skwp @reverbdotcom #wcr14

AR callbacks become more complex as the

system supports more use cases

@skwp @reverbdotcom #wcr14

Different use cases may trigger different events

even when working with the same model

Instead

@skwp @reverbdotcom #wcr14

@skwp @reverbdotcom #wcr14

@skwp @reverbdotcom #wcr14

Add another listener to add behavior The core class doesn’t change

@skwp @reverbdotcom #wcr14

Global listeners for cross-cutting concerns without littering code

Wisper::GlobalListeners.add(Reverb::Listeners::AnalyticsListener.new)"

@skwp @reverbdotcom #wcr14

Controller is a listener too

@skwp @reverbdotcom #wcr14

Policy ObjectsReify complex business rules

into objects

@skwp @reverbdotcom #wcr14

Likely to change

Unlikely to change

@skwp @reverbdotcom #wcr14

refactored for readability

@skwp @reverbdotcom #wcr14

Injectable, but has a default

Simple code, only need to test one pathfor the imperative side effect"

(sending an email)

@skwp @reverbdotcom #wcr14

Distillation Time

@skwp @reverbdotcom #wcr14

Language of codebase reflects language of business

@skwp @reverbdotcom #wcr14

Separate behaviors (what the system does) from models (what the system is)

@skwp @reverbdotcom #wcr14

Rates of Change

@skwp @reverbdotcom #wcr14

Is this all Unicorns and Rainbows?

@skwp @reverbdotcom #wcr14

Sprawl?"Onboarding?"

Naming?"Documentation?"

@skwp @reverbdotcom #wcr14

http://blog.mattwynne.net/category/hexagonal-rails/

http://clean-ruby.com/

Resourceshttp://confreaks.com/videos/759-rubymidwest2011-keynote-

architecture-the-lost-years

http://blog.8thlight.com/uncle-bob/2011/09/30/Screaming-Architecture.html

http://www.artima.com/articles/dci_vision.html

http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html

https://speakerdeck.com/skwp

@skwp @reverbdotcom #wcr14

We Are Hiring Always"

jobs.reverb.com

Ruby, ElasticSearch, DevOps, Designers, Android, and more!

[email protected]