View
1.211
Download
0
Category
Preview:
DESCRIPTION
Modeling large, complex domains "the Rails way” can cause some serious pain. Ruby and Rails are supposed to make developers happy. Let's not allow “the Rails way” and complex domains to take the “happy" out of ruby development. The goal is to allow your Rails application to express the domain so that the domain's business logic is clear-cut, easy to grok, and designed in a way that reduces unnecessary complexities and coupling.
Citation preview
CONFIDENTLY BUILD COMPLEX DOMAINS!
IN RAILS
Mike AbiEzzi
WHAT’S A DOMAIN?
WHY?software gets complex fast
… and nobody wants a train wreck of models
▾ app/! ▸ assets/! ▸ controllers/! ▸ helpers/! ▸ mailers/! ▸ models/! ▸ views/
Developer 1 n
App Store
Customer
App
Installnn
n
n
Purchase
Comment
1
nn
1Version
1
n
1..6
Screenshot
1
1. Process of defining the domain!2. Communicating the domain!3. Relationships between models!4. Aggregates!5. Value Objects!6. Domain Services!7. Data access
1n
App Store
Developer 1 n
Customer
App
Installnn
n
n
Purchase
Comment
1
nn
1
Review!comment!
rating
Version
1
n
1..6
Screenshot
1 Release!version_number
Developer/ CompanySeller
COMMUNICATION
Domain!Expert
Software!Expert
brainstorm → draw diagrams → !speak out assumptions → let them correct you → refine
COMMUNICATION
“Domain experts should object to terms or structures that are
awkward or inadequate to convey domain understanding”
“Developers should watch for ambiguity or inconsistency that will
trip up design.”
-- Eric Evans
UBIQUITOUS LANGUAGE“a common, rigorous language between
developers and users […]
Domain!Expert DeveloperProduct!
Owner
UserDesigner Code
the need for it to be rigorous, since software doesn't cope well with ambiguity”
— Martin Fowler
Ensure one consistent language
app.submit_release(“1.1.0”, ... , screenshots: ["shot1.png", "shot2.png"])
release = Release.new( major: 1, minor: 1, build: 0, ...) release.screenshots << [ Screenshot.new(file: "shot1.png"), Screenshot.new(file: "shot2.png")] release.status = :submitted ! app.releases << release
A BETTER WAY?Describe a domain behavior with a methodEasy to understandSimplifies InteractionManages complexities
Submit a new release of your app
App1
Comment
1n
Review!comment!
rating
n
1..6
Screenshot
1
AGGREGATE
Release!version_number
class App < ActiveRecord::Base ... ! def create_release ... def submit_release ... def approve_release ... def flag_for_abuse ... ! def mark_as_staff_favorite ... ! end
Tells a story of how the domain works
ENTITY VALUEa thing describes a thing
can change state doesn’t reference anything
unique independent!of attributes
avoids design complexities
immutablehas a lifecycle
class Customer class Name
App1
Comment
1n
Review!comment!
rating
n
1..6
Screenshot
1
Value
Value
Entity
Release!version_number
Entity
class Screenshot attr_reader :file, :position def initialize(file, position) @file, @position = file, position end def ==(other) file == other.file && position == other.position end end
VALUE OBJECT immutable / equality
class Screenshot include Comparable attr_reader :file, :position ... ! def <=>(other) position <=> other.position end end
comparableVALUE OBJECT
class VersionNumber attr_reader :major, :minor, :build ... def next_major_version new VersionNumber(major + 1, minor, build) end ! def next_minor_version ... def next_build_version ... end
VALUE OBJECT factories
one-to-many or one-to-one (1-n or 1-1)
C. In its own tableB. Serialized on the Entity’s table
3 ways to persist value objects
A. Inline on the Entity’s tableone-to-one (1-1)
class Release < ActiveRecord::Base def version_number= vn version_number_major = vn.major version_number_minor = vn.minor version_number_build = vn.build end ! def version_number new VersionNumber( version_number_major, version_number_minor, version_number_build) end end
(1-1)A. Inline on the Entity’s table
class Release < ActiveRecord::Base composed_of :version_number, mapping [ %w(version_number_major major), %w(version_number_minor minor), %w(version_number_build build) ] ... end
Release attributes
VersionNumber attributes
Inline on the Entity’s table (1-1)
Gift an App SERVICE
1. Charge the gifter that’s purchasing the app.!
2. Assign access rights to the giftee receiving the app.
Facilitates a transaction between two aggregates
class GiftApp def self.execute(app, gifter, giftee) Purchase.create( app: app, customer: gifter, ...) AccessRight.create( app: app, customer: giftee, purchase: purchase, ...) end end
AccessRight
1n
Purchase
1n
Customer
▾ app/! ▸ models/! ▾ services/! create_app.rb! gift_app.rb! refund_purchase.rb! ...
Data Access App.where( "create_at > ? and purchase_count > ?", 1.week.ago, 10000).all
class App < ActiveRecord::Base scope :new_and_noteworthy, -> { where("create_at > ? and purchases > ?", 1.week.ago, 10000) } end
Use scopes
class App < ActiveRecord::Base scope :new_and_noteworthy, ... scope :staff_picks, ... scope :most_popular, ... ... def create_release(…) def submit_release(…) ... end
One expressive point of entry
* you only need to retrieve aggregate roots *
▾ app/! ▾ models/! ▾ apps/! app.rb! release.rb! review.rb! screenshot.rb! version_number.rb! ▸ customers/! ▸ sellers/! ▸ services/
IN SUMMARY
Continuously refine the domain with a
domain expert
1
Insist on having a ubiquitous language for
seamless communication
2
Constrain relationships to only what the domain needs
3
Create aggregates that express domain concepts and manage complexity
4
Create value objects to eliminate unnecessary
complexity
5
Create domain services to express transactions !between aggregates
6
Express the domain’s intent through!
well defined data access
7
▾ app/! ▸ assets/! ▸ controllers/! ▸ helpers/! ▸ mailers/! ▸ models/! ▸ views/
▾ app/! ▾ models/! ▸ apps/! ▸ customers/! ▸ sellers/! ▸ ...! ▸ services/! create_app.rb! gift_app.rb! refund_purchase.rb! ...
Some Pro Tips1. Don’t let your database diverge to far from
your domain model!
2. Don’t try to build and maintain a huge diagram of your domain!
3. Separate you domain logic from view logic!
4. You can use the gem fig_leaf to privatize ActiveRecord methods (e.g. create, where)
Thanks for Listening!
@mjezzi
www.virtual-genius.com/
www.mikeabiezzi.com
Recommended