SlideShare a Scribd company logo
1 of 63
Download to read offline
Domain Modeling in Ruby on Rails
With introduction to Domain-Driven Design
Paweł Strzałkowski


p.strzalkowski@visuality.pl
Meet Alojzy
Alojzy Meets Rails
Alojzy Loves Rails
Business going off
• $$$


• so
fl
exible and easy!
Months going by...
Complexity kicks in
• Pricing


• Payments


• Shipments


• Returns


• Product Rankings


• Push Noti
fi
cations through Xcompany


• Email API in Ycompany


• Product Recommendations in Zcompany
Alojzy feels lost
• Model


• View


• Controller


... it's obvious that Rails is not enough
Alojzy has enough
If it looks and behaves like a CRUD....
• Create


• Read


• Update


• Delete
Big Ball of Mud
Domain-Driven Design
• software design approach focusing on modelling software to match
a domain according to input from that domain's experts
Domain-Driven Design
• software design approach focusing on modelling software to match
a domain according to input from that domain's experts
Domain-Driven Design
• software design approach focusing on modelling software to match
a domain according to input from that domain's experts
Strategic DDD Elements: Subdomain
Product Catalog
Orders
Shipping
Payments
Warehouse
Strategic DDD Elements: Bounded Context
Product Catalog
Orders
Shipping
Payments
Warehouse
Ecommerce


Context
Availability


Context
Product


Context
Bounded Context
Product Catalog
Orders
Shipping
Payments
Warehouse
Big ball of mud
Bounded Context
Product Catalog
Orders
Shipping
Payments
Warehouse
Product


Context
Order


Context
Billable


Context
Shipping


Context
Availability


Context
Strategic DDD Elements: Ubiquitous Language
Shipping


Context
Shipment
Product
Package
Address
Location Shipping
Ubiquitous Language
Shipping


Context
Shipment
Product
Package
Address
Location Shipping
shipment.update(expected_execution_at: 2.days.since)


shipment.plan_shipping(2.days.since)
Context Map
Identity and Access Context
Product Context
Availability Context
U U
U
D
D
D
OHS / PL OHS / PL
ACL
ACL
Alojzy listens...
Alojzy doubts
oh no....
Tactical DDD
• How to express a model of a domain


• Embrace object oriented programming
Tactical DDD Elements
• Entity


• Value Object


• Aggregate


• Repository


• Factory


• Service


• Domain Event
Tactical DDD Elements: Entity
An object de
fi
ned primarily by its identity is called an ENTITY.


- Eric Evans


• Identity


• Behavior
Tactical DDD Elements: Entity - Identity strategies
• user driven


• application generated


• persistence store generated


• outside
Tactical DDD Elements: Entity - Indispensable behavior
• domain objects have behavior essential to the context


• setter or getter for every primitive do not express behaviour
customer.change_phone_number("+48 123 555 123")


invoice.void(reason: "Client changed his mind")


account.activate
Tactical DDD Elements: Value Object
An object that represents a descriptive aspect of the domain with no
conceptual identity is called a VALUE OBJECT


- Eric Evans


• Represents value


• Immutable


• Comparable


• No identity
Tactical DDD Elements: Aggregate
An AGGREGATE is a cluster of associated objects that we treat as a unit
for the purpose of data changes.


- Eric Evans


• Aggregate Root


• Boundary


• Invariants
Aggregate Root


* invariants
Value Object
Entity
Value Object
Tactical DDD Elements: Aggregate
Address
Client


*max 2 addresses
Contact
Details
address = Addres.new(address_params)


client.add_address(address)
Alojzy doubts
But there is no place for that in Rails
Tactical DDD Elements: Value Object - IRL Example
class TrackEvent


def initialize(user_id:, event:)


self.user_id = user_id


self.event = event


end


def call


return false unless Configuration.enabled?


Client.track(


user_id: user_id,


event: event,


properties: properties


)


end


private


# ...


end
Tactical DDD Elements: Value Object - IRL Example
class TrackEvent


def initialize(user_id:, noun: nil, verb: nil)


self.user_id = user_id


self.event = "#{noun} #{verb}"


end


def call


return false unless Configuration.enabled?


Client.track(


user_id: user_id,


event: event,


properties: properties


)


end


private


# ...


end
Tactical DDD Elements: Value Object - IRL Example
class TrackEvent


def initialize(user_id:, noun: nil, verb: nil, event: nil)


self.user_id = user_id


self.event = event.presence || "#{noun} #{verb}"


end


def call


return false unless Configuration.enabled?


Client.track(


user_id: user_id,


event: event,


properties: properties


)


end


private


# ...


end
Tactical DDD Elements: Value Object - IRL Example
class Event


attr_reader :name


def initialize(name:)


@name = name.to_s


end


def self.combination_of(noun:, verb:)


new(name: "#{noun} #{verb}")


end


def ==(other)


name == other.name


end


end


Event.new(name: 'Job Created')


Event.combination_of(noun: 'Job', verb: 'Started')
Tactical DDD Elements: Value Object - IRL Example
class TrackEvent


def initialize(user_id:, event:)


self.user_id = user_id


self.event = event.name


end


def call


return false unless Configuration.enabled?


Client.track(


user_id: user_id,


event: event,


properties: properties


)


end


private


# ...


end
Tactical DDD Elements: Value Object - RoR Example
class Place < ApplicationRecord


# name, latitude, longitude


end
Tactical DDD Elements: Value Object - RoR Example
class Place < ApplicationRecord


# name, latitude, longitude


composed_of :coordinates


end
Tactical DDD Elements: Value Object - RoR Example
class Place < ApplicationRecord


# name, latitude, longitude


composed_of :coordinates,


class_name: 'Coordinates'


mapping: [%w[latitude latitude], %w[longitude longitude]]


end


class Coordinates


attr_reader :latitude, :longitude


def initialize(latitude, longitude)


@latitude = latitude


@longitude = longitude


end


def ==(other)


latitude == other.latitude && longitude == other.longitude


end


end
Alojzy listens
Interesting...
Domain Modeling in Ruby on Rails - Excercise
Domain Modeling in Ruby on Rails - Excercise
• Customers may be contacted through an email or a phone
Domain Modeling in Ruby on Rails - Excercise
• Customers may be contacted through an email or a phone


• When a customer contacts us, we use their phone numbers to
fi
nd
the records in our books
Domain Modeling in Ruby on Rails - Excercise
• Customers may be contacted through an email or a phone


• When a customer contacts us, we use their phone numbers to
fi
nd
the records in our books


• Each customer has to have a name and a physical address
Domain Modeling in Ruby on Rails - Excercise
• Customers may be contacted through an email or a phone


• When a customer contacts us, we use their phone numbers to
fi
nd
the records in our books


• Each customer has to have a name and a physical address


• Birthdays are very important, we use them for marketing
Domain Modeling in Ruby on Rails - Excercise
• Customers may be contacted through an email or a phone


• When a customer contacts us, we use their phone numbers to
fi
nd
the records in our books


• Each customer has to have a name and a physical address


• Birthdays are very important, we use them for marketing


• We have a loyalty program based on the amount of time customer is
with us, we need to know when a customer was added
Domain Modeling in Ruby on Rails - Excercise
• Customers may be contacted through an email or a phone


• When a customer contacts us, we use their phone numbers to
fi
nd
the records in our books


• Each customer has to have a name and a physical address


• Birthdays are very important, we use them for marketing


• We have a loyalty program based on the amount of time customer is
with us, we need to know when a customer was added


• Customers get into interactions; they can rate others with a positive,
neutral or a negative mark, there can be one rate for one customer
Excercise - ORM-driven
Excercise - ORM-driven
class Customer < ApplicationRecord


validates :city, :street, :name, :birthday, presence: true


validates_with PhoneSyntaxValidator


validates :phone, uniqueness: true


validates_with EmailSyntaxValidator


validates :email, uniqueness: true


validate :birthday_in_the_past


has_many :rates


private


def birthday_in_the_past


errors.add(:birthday, "is in the future") if birthday&.future?


end


end


class Rate < ApplicationRecord


belongs_to :customer


belongs_to :rated_customer, class_name: 'Customer'


end
Excercise - With value objects
Excercise - With value objects
class Address


attr_reader :city, :street


def initialize(city, street)


@city = city


@street = street


end


end


class PersonalInformation


attr_reader :name, :birthday


def initialize(name, birthday)


@name = name


@birthday = birthday


end


end


class Rate < ApplicationRecord


belongs_to :customer


belongs_to :rated_customer,


class_name: 'Customer'


end


class Customer < ApplicationRecord


composed_of :address,


class_name: 'Address',


mapping: [%w[city city], %w[street street]]


validates_with AddressValidator


composed_of :personal_information,


class_name: 'PersonalInformation',


mapping: [


%w[name name], %w[birthday birthday]


]


validates_with PersonalInformationValidator


validates_with PhoneSyntaxValidator


validates :phone, uniqueness: true


validates_with EmailSyntaxValidator


validates :email, uniqueness: true


has_many :rates


end
Excercise - Highlighting identity
class Address


attr_reader :city, :street


def initialize(city, street)


@city = city


@street = street


end


end


class PersonalInformation


attr_reader :name, :birthday, :email


def initialize(name, birthday, email)


@name = name


@birthday = birthday


@email = email


end


end


class Rate < ApplicationRecord


belongs_to :customer


belongs_to :rated_customer,


class_name: 'Customer'


end
class Customer < ApplicationRecord


composed_of :address,


class_name: 'Address',


mapping: [%w[city city], %w[street street]]


validates_with AddressValidator


composed_of :personal_information,


class_name: 'PersonalInformation',


mapping: [


%w[name name], %w[birthday birthday], %[email email]


]


validates_with PersonalInformationValidator


validates_with PhoneSyntaxValidator


validates :phone, uniqueness: true


has_many :rates


end
Excercise - Adding behavior
class Customer < ApplicationRecord


AlreadyRated = Class.new(StandardError)


CannotRateSelf = Class.new(StandardError)


# ...


has_many :rates


def give_negative_rate(other) = give_rate(other, -1)


def give_neutral_rate(other) = give_rate(other, 0)


def give_positive_rate(other) = give_rate(other, 1)


private


def give_rate(other, mark)


raise CannotRateSelf if other == self


raise AlreadyRated if rates.any? { |rate| rate.rated_customer == other }


rates.build(rated_customer: other, mark: mark)


end


end
Excercise - Ugly service
module Customers


class RateService


MarkOutOfBounds = Class.new(StandardError)


AlreadyMarked = Class.new(StandardError)


CannotRateSelf = Class.new(StandardError)


def initialize(customer_id, rated_customer_id, mark)


@customer_id = customer_id


@rated_customer_id = rated_customer_id


@mark = mark


end


def call


raise CannotRateSelf if customer_id == rated_customer_id


customer = Customer.find(@customer_id)


rated_customer_id = Customer.find(@rated_customer_id)


raise MarkOutOfBounds unless [-1, 0, 1].include?(mark)


alread_rated = customer.rates.any? { |rate| rate.rated_customer == rated_customer }


raise AlreadyMarked if alread_rated


customer.rates.build(marked: other, mark: mark)


customer.save


end


end


end
Excercise - Intention revealing service
module Customers


class GivePositiveRateService


def initialize(customer_id, rated_customer_id)


@customer_id = customer_id


@rated_customer_id = rated_customer_id


end


def call


customer = Customer.find(@customer_id)


rated_customer = Customer.find(@rated_customer_id)


customer.give_positive_mark(rated_customer)


customer.save


end


end


end
Excercise - aggregate implementation
module Customers


class GivePositiveRateService


def initialize(customer_id, rated_customer_id)


@customer_id = customer_id


@rated_customer_id = rated_customer_id


end


def call


customer = Customer.find(@customer_id)


rated_customer = Customer.find(@rated_customer_id)


customer.give_positive_mark(rated_customer)


customer.save


end


end


end
Excercise - aggregate implementation
module Customers


class GivePositiveRateService


def initialize(customer_id, rated_customer_id)


@customer_id = customer_id


@rated_customer_id = rated_customer_id


end


def call


customer = Customer.find(@customer_id)


rated_customer = Customer.find(@rated_customer_id)


customer.give_positive_mark(rated_customer)


customer.save


end


end


end


Add optimistic locking
add_column :customers, :lock_version, :integer
We did it!
• Modelled the domain according to knowledge of a domain expert


• Created expressive and intention revealing code


• Guaranteed safety in concurrent operations
How?
• Avoided ORM-based design


• Listened to the domain expert


• Spent minimal time thinking
The job is not done
• Customer model is responsible for handling:


• address used for package delivery


• birthdays used for marketing actions


• email used for transactional email
The job is not done
• Customer model is responsible for handling:


• address used for package delivery


• birthdays used for marketing actions


• email used for transactional email
But that's a story for another time...
References
• Introduction to DDD in Ruby on Rails


• https://www.visuality.pl/posts/introduction-to-ddd-in-ruby-on-rails


• GitHub Repository with used examples


• https://github.com/pstrzalk/ddd-in-ruby-on-rails
Questions?
Comments? Criticism?
Paweł Strzałkowski


p.strzalkowski@visuality.pl


@realPawelS

More Related Content

Similar to Introduction to Domain-Driven Design in Ruby on Rails

E Commerce Web Design Service Proposal PowerPoint Presentation Slides
E Commerce Web Design Service Proposal PowerPoint Presentation SlidesE Commerce Web Design Service Proposal PowerPoint Presentation Slides
E Commerce Web Design Service Proposal PowerPoint Presentation SlidesSlideTeam
 
Using AI for Providing Insights and Recommendations on Activity Data Alexis R...
Using AI for Providing Insights and Recommendations on Activity Data Alexis R...Using AI for Providing Insights and Recommendations on Activity Data Alexis R...
Using AI for Providing Insights and Recommendations on Activity Data Alexis R...Databricks
 
Micro-service architectures with Gilmour
Micro-service architectures with GilmourMicro-service architectures with Gilmour
Micro-service architectures with GilmourAditya Godbole
 
Done in 60 seconds - Creating Web 2.0 applications made easy
Done in 60 seconds - Creating Web 2.0 applications made easyDone in 60 seconds - Creating Web 2.0 applications made easy
Done in 60 seconds - Creating Web 2.0 applications made easyRoel Hartman
 
Up Your Freelancing Game
Up Your Freelancing GameUp Your Freelancing Game
Up Your Freelancing GameJure Cuhalev
 
Online Store Website Design Proposal PowerPoint Presentation Slides
Online Store Website Design Proposal PowerPoint Presentation SlidesOnline Store Website Design Proposal PowerPoint Presentation Slides
Online Store Website Design Proposal PowerPoint Presentation SlidesSlideTeam
 
How to Price Recurring Revenue Services in Your Mobile App Business
How to Price Recurring Revenue Services in Your Mobile App BusinessHow to Price Recurring Revenue Services in Your Mobile App Business
How to Price Recurring Revenue Services in Your Mobile App BusinessKumulos
 
Session 3a The SF SaaS Framework
Session 3a  The SF SaaS FrameworkSession 3a  The SF SaaS Framework
Session 3a The SF SaaS FrameworkCode Mastery
 
E commerce Proposal Template PowerPoint Presentation Slides
E commerce Proposal Template PowerPoint Presentation SlidesE commerce Proposal Template PowerPoint Presentation Slides
E commerce Proposal Template PowerPoint Presentation SlidesSlideTeam
 
Identifying Pricing Request Emails Using Apache Spark and Machine Learning
Identifying Pricing Request Emails Using Apache Spark and Machine LearningIdentifying Pricing Request Emails Using Apache Spark and Machine Learning
Identifying Pricing Request Emails Using Apache Spark and Machine LearningDatabricks
 
The Fine Art of Schema Design in MongoDB: Dos and Don'ts
The Fine Art of Schema Design in MongoDB: Dos and Don'tsThe Fine Art of Schema Design in MongoDB: Dos and Don'ts
The Fine Art of Schema Design in MongoDB: Dos and Don'tsMatias Cascallares
 
Refactoring for microservices
Refactoring for microservicesRefactoring for microservices
Refactoring for microservicesIsmael Rivera
 
Webinar: Realizing Omni-Channel Retailing with MongoDB - One Step at a Time
Webinar: Realizing Omni-Channel Retailing with MongoDB - One Step at a TimeWebinar: Realizing Omni-Channel Retailing with MongoDB - One Step at a Time
Webinar: Realizing Omni-Channel Retailing with MongoDB - One Step at a TimeMongoDB
 
2011-02-03 LA RubyConf Rails3 TDD Workshop
2011-02-03 LA RubyConf Rails3 TDD Workshop2011-02-03 LA RubyConf Rails3 TDD Workshop
2011-02-03 LA RubyConf Rails3 TDD WorkshopWolfram Arnold
 
Test scenarios for sending & receiving emails
Test scenarios for sending & receiving emailsTest scenarios for sending & receiving emails
Test scenarios for sending & receiving emailsMorpheous Algan
 

Similar to Introduction to Domain-Driven Design in Ruby on Rails (20)

E Commerce Web Design Service Proposal PowerPoint Presentation Slides
E Commerce Web Design Service Proposal PowerPoint Presentation SlidesE Commerce Web Design Service Proposal PowerPoint Presentation Slides
E Commerce Web Design Service Proposal PowerPoint Presentation Slides
 
Using AI for Providing Insights and Recommendations on Activity Data Alexis R...
Using AI for Providing Insights and Recommendations on Activity Data Alexis R...Using AI for Providing Insights and Recommendations on Activity Data Alexis R...
Using AI for Providing Insights and Recommendations on Activity Data Alexis R...
 
Micro-service architectures with Gilmour
Micro-service architectures with GilmourMicro-service architectures with Gilmour
Micro-service architectures with Gilmour
 
Done in 60 seconds - Creating Web 2.0 applications made easy
Done in 60 seconds - Creating Web 2.0 applications made easyDone in 60 seconds - Creating Web 2.0 applications made easy
Done in 60 seconds - Creating Web 2.0 applications made easy
 
Up Your Freelancing Game
Up Your Freelancing GameUp Your Freelancing Game
Up Your Freelancing Game
 
Online Store Website Design Proposal PowerPoint Presentation Slides
Online Store Website Design Proposal PowerPoint Presentation SlidesOnline Store Website Design Proposal PowerPoint Presentation Slides
Online Store Website Design Proposal PowerPoint Presentation Slides
 
How to Price Recurring Revenue Services in Your Mobile App Business
How to Price Recurring Revenue Services in Your Mobile App BusinessHow to Price Recurring Revenue Services in Your Mobile App Business
How to Price Recurring Revenue Services in Your Mobile App Business
 
Restructuring rails
Restructuring railsRestructuring rails
Restructuring rails
 
Ankur Bajad
Ankur BajadAnkur Bajad
Ankur Bajad
 
Angular js firebase-preso
Angular js firebase-presoAngular js firebase-preso
Angular js firebase-preso
 
Session 3a The SF SaaS Framework
Session 3a  The SF SaaS FrameworkSession 3a  The SF SaaS Framework
Session 3a The SF SaaS Framework
 
Vishnu Updated
Vishnu UpdatedVishnu Updated
Vishnu Updated
 
E commerce Proposal Template PowerPoint Presentation Slides
E commerce Proposal Template PowerPoint Presentation SlidesE commerce Proposal Template PowerPoint Presentation Slides
E commerce Proposal Template PowerPoint Presentation Slides
 
Identifying Pricing Request Emails Using Apache Spark and Machine Learning
Identifying Pricing Request Emails Using Apache Spark and Machine LearningIdentifying Pricing Request Emails Using Apache Spark and Machine Learning
Identifying Pricing Request Emails Using Apache Spark and Machine Learning
 
The Fine Art of Schema Design in MongoDB: Dos and Don'ts
The Fine Art of Schema Design in MongoDB: Dos and Don'tsThe Fine Art of Schema Design in MongoDB: Dos and Don'ts
The Fine Art of Schema Design in MongoDB: Dos and Don'ts
 
Refactoring for microservices
Refactoring for microservicesRefactoring for microservices
Refactoring for microservices
 
Webinar: Realizing Omni-Channel Retailing with MongoDB - One Step at a Time
Webinar: Realizing Omni-Channel Retailing with MongoDB - One Step at a TimeWebinar: Realizing Omni-Channel Retailing with MongoDB - One Step at a Time
Webinar: Realizing Omni-Channel Retailing with MongoDB - One Step at a Time
 
#CNX14 - Intro to Force
#CNX14 - Intro to Force#CNX14 - Intro to Force
#CNX14 - Intro to Force
 
2011-02-03 LA RubyConf Rails3 TDD Workshop
2011-02-03 LA RubyConf Rails3 TDD Workshop2011-02-03 LA RubyConf Rails3 TDD Workshop
2011-02-03 LA RubyConf Rails3 TDD Workshop
 
Test scenarios for sending & receiving emails
Test scenarios for sending & receiving emailsTest scenarios for sending & receiving emails
Test scenarios for sending & receiving emails
 

More from Visuality

3 issues that made 30 test workers take 40 minutes
3 issues that made 30 test workers take 40 minutes3 issues that made 30 test workers take 40 minutes
3 issues that made 30 test workers take 40 minutesVisuality
 
Czego nie robić przy pisaniu testów
Czego nie robić przy pisaniu testówCzego nie robić przy pisaniu testów
Czego nie robić przy pisaniu testówVisuality
 
Active Record .includes - do you use it consciously?
Active Record .includes - do you use it consciously?Active Record .includes - do you use it consciously?
Active Record .includes - do you use it consciously?Visuality
 
Introduction to Event Storming
Introduction to Event StormingIntroduction to Event Storming
Introduction to Event StormingVisuality
 
Jak programowanie może pomóc na co dzień?
Jak programowanie może pomóc na co dzień?Jak programowanie może pomóc na co dzień?
Jak programowanie może pomóc na co dzień?Visuality
 
SVG Overview - How To Draw, Use and Animate
SVG Overview - How To Draw, Use and AnimateSVG Overview - How To Draw, Use and Animate
SVG Overview - How To Draw, Use and AnimateVisuality
 
How To Migrate a Rails App From a Dedicated Server Into Cloud Environment? - ...
How To Migrate a Rails App From a Dedicated Server Into Cloud Environment? - ...How To Migrate a Rails App From a Dedicated Server Into Cloud Environment? - ...
How To Migrate a Rails App From a Dedicated Server Into Cloud Environment? - ...Visuality
 
How to use AWS SES with Lambda 
in Ruby on Rails application - Michał Łęcicki
How to use AWS SES with Lambda 
in Ruby on Rails application - Michał ŁęcickiHow to use AWS SES with Lambda 
in Ruby on Rails application - Michał Łęcicki
How to use AWS SES with Lambda 
in Ruby on Rails application - Michał ŁęcickiVisuality
 
What is NOT machine learning - Burak Aybar
What is NOT machine learning - Burak AybarWhat is NOT machine learning - Burak Aybar
What is NOT machine learning - Burak AybarVisuality
 
Do you really need to reload?
Do you really need to reload?Do you really need to reload?
Do you really need to reload?Visuality
 
How to check valid email? Find using regex(p?)
How to check valid email? Find using regex(p?)How to check valid email? Find using regex(p?)
How to check valid email? Find using regex(p?)Visuality
 
Fantastic stresses and where to find them
Fantastic stresses and where to find themFantastic stresses and where to find them
Fantastic stresses and where to find themVisuality
 
Fuzzy search in Ruby
Fuzzy search in RubyFuzzy search in Ruby
Fuzzy search in RubyVisuality
 
Rfc process in visuality
Rfc process in visualityRfc process in visuality
Rfc process in visualityVisuality
 
GraphQL in Ruby on Rails - basics
GraphQL in Ruby on Rails - basicsGraphQL in Ruby on Rails - basics
GraphQL in Ruby on Rails - basicsVisuality
 
Consumer Driven Contracts
Consumer Driven ContractsConsumer Driven Contracts
Consumer Driven ContractsVisuality
 
How do we use CircleCi in Laterallink?
How do we use CircleCi in Laterallink?How do we use CircleCi in Laterallink?
How do we use CircleCi in Laterallink?Visuality
 
React Native - Short introduction
React Native - Short introductionReact Native - Short introduction
React Native - Short introductionVisuality
 
Risk in project management
Risk in project managementRisk in project management
Risk in project managementVisuality
 
Ruby formatters
Ruby formattersRuby formatters
Ruby formattersVisuality
 

More from Visuality (20)

3 issues that made 30 test workers take 40 minutes
3 issues that made 30 test workers take 40 minutes3 issues that made 30 test workers take 40 minutes
3 issues that made 30 test workers take 40 minutes
 
Czego nie robić przy pisaniu testów
Czego nie robić przy pisaniu testówCzego nie robić przy pisaniu testów
Czego nie robić przy pisaniu testów
 
Active Record .includes - do you use it consciously?
Active Record .includes - do you use it consciously?Active Record .includes - do you use it consciously?
Active Record .includes - do you use it consciously?
 
Introduction to Event Storming
Introduction to Event StormingIntroduction to Event Storming
Introduction to Event Storming
 
Jak programowanie może pomóc na co dzień?
Jak programowanie może pomóc na co dzień?Jak programowanie może pomóc na co dzień?
Jak programowanie może pomóc na co dzień?
 
SVG Overview - How To Draw, Use and Animate
SVG Overview - How To Draw, Use and AnimateSVG Overview - How To Draw, Use and Animate
SVG Overview - How To Draw, Use and Animate
 
How To Migrate a Rails App From a Dedicated Server Into Cloud Environment? - ...
How To Migrate a Rails App From a Dedicated Server Into Cloud Environment? - ...How To Migrate a Rails App From a Dedicated Server Into Cloud Environment? - ...
How To Migrate a Rails App From a Dedicated Server Into Cloud Environment? - ...
 
How to use AWS SES with Lambda 
in Ruby on Rails application - Michał Łęcicki
How to use AWS SES with Lambda 
in Ruby on Rails application - Michał ŁęcickiHow to use AWS SES with Lambda 
in Ruby on Rails application - Michał Łęcicki
How to use AWS SES with Lambda 
in Ruby on Rails application - Michał Łęcicki
 
What is NOT machine learning - Burak Aybar
What is NOT machine learning - Burak AybarWhat is NOT machine learning - Burak Aybar
What is NOT machine learning - Burak Aybar
 
Do you really need to reload?
Do you really need to reload?Do you really need to reload?
Do you really need to reload?
 
How to check valid email? Find using regex(p?)
How to check valid email? Find using regex(p?)How to check valid email? Find using regex(p?)
How to check valid email? Find using regex(p?)
 
Fantastic stresses and where to find them
Fantastic stresses and where to find themFantastic stresses and where to find them
Fantastic stresses and where to find them
 
Fuzzy search in Ruby
Fuzzy search in RubyFuzzy search in Ruby
Fuzzy search in Ruby
 
Rfc process in visuality
Rfc process in visualityRfc process in visuality
Rfc process in visuality
 
GraphQL in Ruby on Rails - basics
GraphQL in Ruby on Rails - basicsGraphQL in Ruby on Rails - basics
GraphQL in Ruby on Rails - basics
 
Consumer Driven Contracts
Consumer Driven ContractsConsumer Driven Contracts
Consumer Driven Contracts
 
How do we use CircleCi in Laterallink?
How do we use CircleCi in Laterallink?How do we use CircleCi in Laterallink?
How do we use CircleCi in Laterallink?
 
React Native - Short introduction
React Native - Short introductionReact Native - Short introduction
React Native - Short introduction
 
Risk in project management
Risk in project managementRisk in project management
Risk in project management
 
Ruby formatters
Ruby formattersRuby formatters
Ruby formatters
 

Recently uploaded

Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 3652toLead Limited
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxNavinnSomaal
 
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxA Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxLoriGlavin3
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Commit University
 
Generative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information DevelopersGenerative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information DevelopersRaghuram Pandurangan
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc
 
Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteDianaGray10
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationSlibray Presentation
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyAlfredo García Lavilla
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsRizwan Syed
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek SchlawackFwdays
 
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptxUse of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptxLoriGlavin3
 
Unraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfUnraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfAlex Barbosa Coqueiro
 
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptxThe Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptxLoriGlavin3
 
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024BookNet Canada
 
SALESFORCE EDUCATION CLOUD | FEXLE SERVICES
SALESFORCE EDUCATION CLOUD | FEXLE SERVICESSALESFORCE EDUCATION CLOUD | FEXLE SERVICES
SALESFORCE EDUCATION CLOUD | FEXLE SERVICESmohitsingh558521
 
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdfHyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdfPrecisely
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Mattias Andersson
 
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 

Recently uploaded (20)

Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptx
 
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxA Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!
 
Generative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information DevelopersGenerative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information Developers
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
 
Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test Suite
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck Presentation
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easy
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL Certs
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
 
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptxUse of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
 
Unraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfUnraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdf
 
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptxThe Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
 
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
 
SALESFORCE EDUCATION CLOUD | FEXLE SERVICES
SALESFORCE EDUCATION CLOUD | FEXLE SERVICESSALESFORCE EDUCATION CLOUD | FEXLE SERVICES
SALESFORCE EDUCATION CLOUD | FEXLE SERVICES
 
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdfHyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?
 
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 

Introduction to Domain-Driven Design in Ruby on Rails

  • 1. Domain Modeling in Ruby on Rails With introduction to Domain-Driven Design Paweł Strzałkowski p.strzalkowski@visuality.pl
  • 5. Business going off • $$$ • so fl exible and easy!
  • 7. Complexity kicks in • Pricing • Payments • Shipments • Returns • Product Rankings • Push Noti fi cations through Xcompany • Email API in Ycompany • Product Recommendations in Zcompany
  • 8. Alojzy feels lost • Model • View • Controller ... it's obvious that Rails is not enough
  • 10. If it looks and behaves like a CRUD.... • Create • Read • Update • Delete
  • 11. Big Ball of Mud
  • 12. Domain-Driven Design • software design approach focusing on modelling software to match a domain according to input from that domain's experts
  • 13. Domain-Driven Design • software design approach focusing on modelling software to match a domain according to input from that domain's experts
  • 14. Domain-Driven Design • software design approach focusing on modelling software to match a domain according to input from that domain's experts
  • 15. Strategic DDD Elements: Subdomain Product Catalog Orders Shipping Payments Warehouse
  • 16. Strategic DDD Elements: Bounded Context Product Catalog Orders Shipping Payments Warehouse Ecommerce Context Availability Context Product Context
  • 19. Strategic DDD Elements: Ubiquitous Language Shipping Context Shipment Product Package Address Location Shipping
  • 21. Context Map Identity and Access Context Product Context Availability Context U U U D D D OHS / PL OHS / PL ACL ACL
  • 24. Tactical DDD • How to express a model of a domain • Embrace object oriented programming
  • 25. Tactical DDD Elements • Entity • Value Object • Aggregate • Repository • Factory • Service • Domain Event
  • 26. Tactical DDD Elements: Entity An object de fi ned primarily by its identity is called an ENTITY. - Eric Evans • Identity • Behavior
  • 27. Tactical DDD Elements: Entity - Identity strategies • user driven • application generated • persistence store generated • outside
  • 28. Tactical DDD Elements: Entity - Indispensable behavior • domain objects have behavior essential to the context • setter or getter for every primitive do not express behaviour customer.change_phone_number("+48 123 555 123") invoice.void(reason: "Client changed his mind") account.activate
  • 29. Tactical DDD Elements: Value Object An object that represents a descriptive aspect of the domain with no conceptual identity is called a VALUE OBJECT - Eric Evans • Represents value • Immutable • Comparable • No identity
  • 30. Tactical DDD Elements: Aggregate An AGGREGATE is a cluster of associated objects that we treat as a unit for the purpose of data changes. - Eric Evans • Aggregate Root • Boundary • Invariants Aggregate Root * invariants Value Object Entity Value Object
  • 31. Tactical DDD Elements: Aggregate Address Client *max 2 addresses Contact Details address = Addres.new(address_params) client.add_address(address)
  • 32. Alojzy doubts But there is no place for that in Rails
  • 33. Tactical DDD Elements: Value Object - IRL Example class TrackEvent def initialize(user_id:, event:) self.user_id = user_id self.event = event end def call return false unless Configuration.enabled? Client.track( user_id: user_id, event: event, properties: properties ) end private # ... end
  • 34. Tactical DDD Elements: Value Object - IRL Example class TrackEvent def initialize(user_id:, noun: nil, verb: nil) self.user_id = user_id self.event = "#{noun} #{verb}" end def call return false unless Configuration.enabled? Client.track( user_id: user_id, event: event, properties: properties ) end private # ... end
  • 35. Tactical DDD Elements: Value Object - IRL Example class TrackEvent def initialize(user_id:, noun: nil, verb: nil, event: nil) self.user_id = user_id self.event = event.presence || "#{noun} #{verb}" end def call return false unless Configuration.enabled? Client.track( user_id: user_id, event: event, properties: properties ) end private # ... end
  • 36. Tactical DDD Elements: Value Object - IRL Example class Event attr_reader :name def initialize(name:) @name = name.to_s end def self.combination_of(noun:, verb:) new(name: "#{noun} #{verb}") end def ==(other) name == other.name end end Event.new(name: 'Job Created') Event.combination_of(noun: 'Job', verb: 'Started')
  • 37. Tactical DDD Elements: Value Object - IRL Example class TrackEvent def initialize(user_id:, event:) self.user_id = user_id self.event = event.name end def call return false unless Configuration.enabled? Client.track( user_id: user_id, event: event, properties: properties ) end private # ... end
  • 38. Tactical DDD Elements: Value Object - RoR Example class Place < ApplicationRecord # name, latitude, longitude end
  • 39. Tactical DDD Elements: Value Object - RoR Example class Place < ApplicationRecord # name, latitude, longitude composed_of :coordinates end
  • 40. Tactical DDD Elements: Value Object - RoR Example class Place < ApplicationRecord # name, latitude, longitude composed_of :coordinates, class_name: 'Coordinates' mapping: [%w[latitude latitude], %w[longitude longitude]] end class Coordinates attr_reader :latitude, :longitude def initialize(latitude, longitude) @latitude = latitude @longitude = longitude end def ==(other) latitude == other.latitude && longitude == other.longitude end end
  • 42. Domain Modeling in Ruby on Rails - Excercise
  • 43. Domain Modeling in Ruby on Rails - Excercise • Customers may be contacted through an email or a phone
  • 44. Domain Modeling in Ruby on Rails - Excercise • Customers may be contacted through an email or a phone • When a customer contacts us, we use their phone numbers to fi nd the records in our books
  • 45. Domain Modeling in Ruby on Rails - Excercise • Customers may be contacted through an email or a phone • When a customer contacts us, we use their phone numbers to fi nd the records in our books • Each customer has to have a name and a physical address
  • 46. Domain Modeling in Ruby on Rails - Excercise • Customers may be contacted through an email or a phone • When a customer contacts us, we use their phone numbers to fi nd the records in our books • Each customer has to have a name and a physical address • Birthdays are very important, we use them for marketing
  • 47. Domain Modeling in Ruby on Rails - Excercise • Customers may be contacted through an email or a phone • When a customer contacts us, we use their phone numbers to fi nd the records in our books • Each customer has to have a name and a physical address • Birthdays are very important, we use them for marketing • We have a loyalty program based on the amount of time customer is with us, we need to know when a customer was added
  • 48. Domain Modeling in Ruby on Rails - Excercise • Customers may be contacted through an email or a phone • When a customer contacts us, we use their phone numbers to fi nd the records in our books • Each customer has to have a name and a physical address • Birthdays are very important, we use them for marketing • We have a loyalty program based on the amount of time customer is with us, we need to know when a customer was added • Customers get into interactions; they can rate others with a positive, neutral or a negative mark, there can be one rate for one customer
  • 50. Excercise - ORM-driven class Customer < ApplicationRecord validates :city, :street, :name, :birthday, presence: true validates_with PhoneSyntaxValidator validates :phone, uniqueness: true validates_with EmailSyntaxValidator validates :email, uniqueness: true validate :birthday_in_the_past has_many :rates private def birthday_in_the_past errors.add(:birthday, "is in the future") if birthday&.future? end end class Rate < ApplicationRecord belongs_to :customer belongs_to :rated_customer, class_name: 'Customer' end
  • 51. Excercise - With value objects
  • 52. Excercise - With value objects class Address attr_reader :city, :street def initialize(city, street) @city = city @street = street end end class PersonalInformation attr_reader :name, :birthday def initialize(name, birthday) @name = name @birthday = birthday end end class Rate < ApplicationRecord belongs_to :customer belongs_to :rated_customer, class_name: 'Customer' end class Customer < ApplicationRecord composed_of :address, class_name: 'Address', mapping: [%w[city city], %w[street street]] validates_with AddressValidator composed_of :personal_information, class_name: 'PersonalInformation', mapping: [ %w[name name], %w[birthday birthday] ] validates_with PersonalInformationValidator validates_with PhoneSyntaxValidator validates :phone, uniqueness: true validates_with EmailSyntaxValidator validates :email, uniqueness: true has_many :rates end
  • 53. Excercise - Highlighting identity class Address attr_reader :city, :street def initialize(city, street) @city = city @street = street end end class PersonalInformation attr_reader :name, :birthday, :email def initialize(name, birthday, email) @name = name @birthday = birthday @email = email end end class Rate < ApplicationRecord belongs_to :customer belongs_to :rated_customer, class_name: 'Customer' end class Customer < ApplicationRecord composed_of :address, class_name: 'Address', mapping: [%w[city city], %w[street street]] validates_with AddressValidator composed_of :personal_information, class_name: 'PersonalInformation', mapping: [ %w[name name], %w[birthday birthday], %[email email] ] validates_with PersonalInformationValidator validates_with PhoneSyntaxValidator validates :phone, uniqueness: true has_many :rates end
  • 54. Excercise - Adding behavior class Customer < ApplicationRecord AlreadyRated = Class.new(StandardError) CannotRateSelf = Class.new(StandardError) # ... has_many :rates def give_negative_rate(other) = give_rate(other, -1) def give_neutral_rate(other) = give_rate(other, 0) def give_positive_rate(other) = give_rate(other, 1) private def give_rate(other, mark) raise CannotRateSelf if other == self raise AlreadyRated if rates.any? { |rate| rate.rated_customer == other } rates.build(rated_customer: other, mark: mark) end end
  • 55. Excercise - Ugly service module Customers class RateService MarkOutOfBounds = Class.new(StandardError) AlreadyMarked = Class.new(StandardError) CannotRateSelf = Class.new(StandardError) def initialize(customer_id, rated_customer_id, mark) @customer_id = customer_id @rated_customer_id = rated_customer_id @mark = mark end def call raise CannotRateSelf if customer_id == rated_customer_id customer = Customer.find(@customer_id) rated_customer_id = Customer.find(@rated_customer_id) raise MarkOutOfBounds unless [-1, 0, 1].include?(mark) alread_rated = customer.rates.any? { |rate| rate.rated_customer == rated_customer } raise AlreadyMarked if alread_rated customer.rates.build(marked: other, mark: mark) customer.save end end end
  • 56. Excercise - Intention revealing service module Customers class GivePositiveRateService def initialize(customer_id, rated_customer_id) @customer_id = customer_id @rated_customer_id = rated_customer_id end def call customer = Customer.find(@customer_id) rated_customer = Customer.find(@rated_customer_id) customer.give_positive_mark(rated_customer) customer.save end end end
  • 57. Excercise - aggregate implementation module Customers class GivePositiveRateService def initialize(customer_id, rated_customer_id) @customer_id = customer_id @rated_customer_id = rated_customer_id end def call customer = Customer.find(@customer_id) rated_customer = Customer.find(@rated_customer_id) customer.give_positive_mark(rated_customer) customer.save end end end
  • 58. Excercise - aggregate implementation module Customers class GivePositiveRateService def initialize(customer_id, rated_customer_id) @customer_id = customer_id @rated_customer_id = rated_customer_id end def call customer = Customer.find(@customer_id) rated_customer = Customer.find(@rated_customer_id) customer.give_positive_mark(rated_customer) customer.save end end end Add optimistic locking add_column :customers, :lock_version, :integer
  • 59. We did it! • Modelled the domain according to knowledge of a domain expert • Created expressive and intention revealing code • Guaranteed safety in concurrent operations How? • Avoided ORM-based design • Listened to the domain expert • Spent minimal time thinking
  • 60. The job is not done • Customer model is responsible for handling: • address used for package delivery • birthdays used for marketing actions • email used for transactional email
  • 61. The job is not done • Customer model is responsible for handling: • address used for package delivery • birthdays used for marketing actions • email used for transactional email But that's a story for another time...
  • 62. References • Introduction to DDD in Ruby on Rails • https://www.visuality.pl/posts/introduction-to-ddd-in-ruby-on-rails • GitHub Repository with used examples • https://github.com/pstrzalk/ddd-in-ruby-on-rails