45
Domain Driven Domain Driven Design Design in Ruby on Rails in Ruby on Rails Created by Angelo Maron

Domain Driven Design in Rails

Embed Size (px)

DESCRIPTION

Presentation on SolutionCamp 2014 in Hamburg. Domain Driven Design with Ruby on Rails. What can we do with the stuff Rails/Ruby gives us.

Citation preview

Page 1: Domain Driven Design in Rails

Domain DrivenDomain DrivenDesignDesign

in Ruby on Railsin Ruby on RailsCreated by Angelo Maron

Page 2: Domain Driven Design in Rails

Wer bin ich?Wer bin ich?

Angelo MaronAngelo Maron

Sofware-Entwickler seit ca. 7 Jahren (Ruby on Rails)bei AKRA seit 2,5 Jahren

Xing: https://www.xing.com/profile/Angelo_MaronTwitter: @MrRaffnix

Page 3: Domain Driven Design in Rails

AgendaAgenda1. MVC & Rails2. Domain Driven Design3. Domain Driven Design in Rails4. Zusammenfassung

Page 4: Domain Driven Design in Rails

MVCMVC(Model View Controller)(Model View Controller)

Wikipedia:Wikipedia:

Der englischsprachige Begriff "model viewcontroller" (MVC) ist ein Muster zur

Strukturierung von Software-Entwicklung in diedrei Einheiten Datenmodell (engl. model),

Präsentation (engl. view) undProgrammsteuerung (engl. controller).

Page 5: Domain Driven Design in Rails

Ruby on RailsRuby on RailsRuby on Rails ist ein in der Programmiersprache Rubygeschriebenes und quelloffenes Web Application Framework.wird dieses Jahr 10 Jahre alt.basiert auf dem MVC-PatternPrinzipien:Prinzipien:

Don't repeat yourself (DRY)Convention over Configuration

Page 6: Domain Driven Design in Rails

!Convention!!Convention!

Page 7: Domain Driven Design in Rails

MVC in Ruby on RailsMVC in Ruby on RailsM odel: ActiveRecord

V iew: ActionView

C ontroller: ActionController

Page 8: Domain Driven Design in Rails

Fat Model, Skinny Con-Fat Model, Skinny Con-troller (1)troller (1)

"Im Zusammenspiel von Controller und Model,versuche im Controller nur das Objekt zuinstanzieren und einen Methodenaufrufdurchzuführen. Der Rest wird im Modeldefiniert."

Page 9: Domain Driven Design in Rails

Fat Model, Skinny Con-Fat Model, Skinny Con-troller (2)troller (2)

def index @published_posts = Post.all.where('published_at <= ?', Time.now) @unpublished_posts = Post.all.where('published_at IS NULL or published_at > ?', Time.now)end

def index @published_posts = Post.all_published @unpublished_posts = Post.all_unpublishedend

Page 10: Domain Driven Design in Rails

ProblematikProblematikMit zunehmenden Anforderungen werden können die folgendenProblematiken auftreten:

Die Model-Dateien werden immer größer (500+ Zeilen)Die Actions der Controller werden immer größer (30+ Zeilen)

Dies führt zur Verschlechterung der Testbarkeit, Wartbarkeit undVerständnis des Codes.Daher habe ich versucht nach den folgenden Regeln zu arbeiten.(nach Sandy Matz)

Eine Klasse hat maximal 100 Zeilen.Eine Methode hat maximal 5 Zeilen.

--> Domain Driven Design--> Domain Driven Design

Page 11: Domain Driven Design in Rails

Domain Driven DesignDomain Driven DesignDomain-Driven Design (DDD) ist eine

Herangehensweise an die Modellierungkomplexer objektorientierter Software. Die

Modellierung der Software wird dabeimaßgeblich von den umzusetzenden

Fachlichkeiten der Anwendungsdomänebeeinflusst.

Page 12: Domain Driven Design in Rails

Warum DDD?Warum DDD?1. Fachliche Begrifflichkeiten und Codestruktur angleichen.2. kleinere Dateien (bessere Wartbarkeit)3. höhere Codeunabhängigkeit (bessere Wartbarkeit)4. Bessere Testbarkeit (durch kleinere Klassen)

Page 13: Domain Driven Design in Rails

Domain-Driven Design inDomain-Driven Design inRailsRails

Rails HelpersConcernsService ObjectsPresenter ObjectsStrukturierte NamensräumeDecorator Pattern

Page 14: Domain Driven Design in Rails

Rails HelpersRails HelpersInterne Struktur von Rails um Funktionen für views zur Verfügung

zu stellen.Funktionen um komplexen Code aus der View zu halten.Sich wiederholende Funktionen wieder zu verwenden.Persönlich: (erinnert mich an funktionale Programmierung)

Page 15: Domain Driven Design in Rails

Beispiel Rails Helper (1)Beispiel Rails Helper (1)

<div> <%- if @user.image.present? %> <%= image_tag(@user.image.path) %> <%- else %> <%= image_tag(asset_path('default_profile.png')) %> <%- end %></div>

Page 16: Domain Driven Design in Rails

Beispiel Rails Helper (2)Beispiel Rails Helper (2)

module UserHelper def user_picture_path(user) if user.image.present? user.image.path else asset_path('default_profile.png') end endend

<div> <%= image_tag(user_picture_path(@user)) %></div>

Page 17: Domain Driven Design in Rails

Domain-Driven Design inDomain-Driven Design inRailsRails

Rails HelpersConcernsService ObjectsPresenter ObjectsStrukturierte NamensräumeDecorator Pattern

Page 18: Domain Driven Design in Rails

Rails ConcernsRails ConcernsRails Concerns ist eine Möglichkeit Code in Module auszulagern,die man über ein include in ein beliebiges Model inkludieren kann.

Dieses hat folgende Anwendungsmöglichkeiten:1. Aufteilen des Codes eines Models in mehrere Dateien2. Auslagerung von Mechanismen/Pattern zur Wiederverwendung

Rails bietet hier ein hauseigenes Konzept: ActiveSupport::Concern

Page 19: Domain Driven Design in Rails

Beispiel Rails Concerns (1)Beispiel Rails Concerns (1)

class Article < ActiveRecord::Base belongs_to :create_user, class_name: 'User' belongs_to :update_user, class_name: 'User'end

class Job < ActiveRecord::Base belongs_to :create_user, class_name: 'User' belongs_to :update_user, class_name: 'User'end

Page 20: Domain Driven Design in Rails

Beispiel Rails Concerns (2)Beispiel Rails Concerns (2)

module Trackable extend ActiveSupport::Concern

included do belongs_to :create_user, class_name: 'User' belongs_to :update_user, class_name: 'User' endend

class Article < ActiveRecord::Base include Trackableend

class Job < ActiveRecord::Base include Trackableend

Page 21: Domain Driven Design in Rails

Domain-Driven Design inDomain-Driven Design inRailsRails

Rails HelpersConcernsService ObjectsPresenter ObjectsStrukturierte NamensräumeDecorator Pattern

Page 22: Domain Driven Design in Rails

Service ObjectsService ObjectsVerlagerung bestimmter Funktionen eines Anwendungsfall in ein

eigenes Objekt, dass genau diese Funktionen zur Verfügung stellt.zum Beispiel:

SearchService (Suchkomponente)InvoiceService (Rechnungserstellung)RegistrationService (RegistrierungsValidierung)

Page 23: Domain Driven Design in Rails

Beispiel Service Object (1)Beispiel Service Object (1)

class Article < ActiveRecord::Base def search(term, options = {}) # execute search end

def admin_search(term, options = {}) # execute search from admin perspective endend

def Job < ActiveRecord::Base def search(term, options = {}) # execute search end

def admin_search(term, options = {}) # execute search from admin perspective endend

Page 24: Domain Driven Design in Rails

Beispiel Service Object (2)Beispiel Service Object (2)

class SearchService def self.article_search(term, options = {}) # execute search here end

def self.job_search(term, options = {}) # execute search here endend

class AdminSearchService def self.article_search(term, options = {}) # execute search here end

def self.job_search(term, options = {}) # execute search here endend

Page 25: Domain Driven Design in Rails

Domain-Driven Design inDomain-Driven Design inRailsRails

Rails HelpersConcernsService ObjectsPresenter ObjectsStrukturierte NamensräumeDecorator Pattern

Page 26: Domain Driven Design in Rails

Presenter ObjectsPresenter ObjectsOft haben Get-Operationen komplexe Analyse von Parametern,

um die genau angeforderten Objekte anzuzeigen. Hier gibt es einegute Möglichkeit, diese in eigene Objekte auszulagern.

Page 27: Domain Driven Design in Rails

Beispiel Presenter ObjectBeispiel Presenter Object(1)(1)

class JobController < ApplicationController::Base def index @jobs = Job.active.preload(:items) if params[:state] @jobs = @jobs.by_state(params[:state]) end

if params[:order] order_string = "#{params[:order]} #{params[:dir].presence || 'asc'}" @jobs = @jobs.order(order_string) end

@jobs = @jobs.page(params[:page].presence || 1).per(params[:per].presence || 10) endend

Page 28: Domain Driven Design in Rails

Beispiel Presenter ObjectBeispiel Presenter Object(2)(2)

class JobController < ApplicationController::Base def index @jobs = JobPresenter.get_all(params) endend

class JobPresenter def self.get_all(params) self.new(params).get_all end

def new(params) @params = params end

def get_all jobs = Job.active.preload(:items) if @params[:state] jobs = jobs.by_state(@params[:state]) end

Page 29: Domain Driven Design in Rails

Domain-Driven Design inDomain-Driven Design inRailsRails

Rails HelpersConcernsService ObjectsPresenter ObjectsStrukturierte NamensräumeDecorator Pattern

Page 30: Domain Driven Design in Rails

Strukturierte Namen-Strukturierte Namen-sräumesräume

Strukturierung aller Objekte (Models, Services, Presenters,Decorators, Controllers usw.) in Domain-basierte Namespaces.

Dies hat folgende Vorteile:Code-Unabhängigkeitkleinere Dateienbessere Abbildung der Businessdomäne im Code.

Page 31: Domain Driven Design in Rails

Beispiel Namensräume (1)Beispiel Namensräume (1)

class Job < ActiveRecord::Base def self.all_for_admins #execute query here end

def self.all_for_moderators # execute query here end

def self.all_for_visitors #execute query here endend

Page 32: Domain Driven Design in Rails

Beispiel Namensräume (2)Beispiel Namensräume (2)

module Admins class Job def self.all #execute query here end endend

module Moderators class Job def self.all #execute query here end endend

module Visitors class Job def self.all #execute query here end endend

Page 33: Domain Driven Design in Rails

Domain-Driven Design inDomain-Driven Design inRailsRails

Rails HelpersConcernsService ObjectsPresenter ObjectsStrukturierte NamensräumeDecorator Pattern

Page 34: Domain Driven Design in Rails

Decorators in Rails (1)Decorators in Rails (1)

Page 35: Domain Driven Design in Rails

Decorators in Rails (2)Decorators in Rails (2)

Page 36: Domain Driven Design in Rails

Beispiel Decorator (1)Beispiel Decorator (1)

class UserController < ApplicationController::Base def show @user = User.find(params[:id]) endend

<div> <p>Erstellt am: <%= @user.registered_at.strftime('%Y%m%d')</p> <p>Name: <%= @user.first_name + @user.last_name %></p> <%- if @user.image.present? %> <%= image_tag(@user.image.path) %> <%- else %> <%= image_tag(asset_path('default_profile.png')) %> <%- end %></div>

Page 37: Domain Driven Design in Rails

Beispiel Decorator (2)Beispiel Decorator (2)

class UserDecorator < SimpleDelegator

def registered_at @object.registered_at.strftime('%Y%m%d') end

def name "#{@object.first_name} #{@object.last_name}" end

def image_path # return image.path or default endend

class UserController < ApplicationController::Base def show @user_decorator = UserDecorator.new(User.find(params[:id])) endend

<div> <p>Erstellt am: <%= @user_decorator.registered_at</p>

Page 38: Domain Driven Design in Rails

Und was nun?Und was nun?

Page 39: Domain Driven Design in Rails

Die alles entscheideneDie alles entscheideneFrageFrage

Welches Pattern benutzt man wann?Ab wann benutzen wir überhaupt diese Pattern?

Page 40: Domain Driven Design in Rails

NeuprogrammierungNeuprogrammierungAbwägung der Komplexität gegen den entstehenden Aufwand.Wichtig: Komplexe Business-Logik lässt sich nur durch komplexenCode abbilden.

Page 41: Domain Driven Design in Rails

RefactoringRefactoringOft ist schon ein gewisser Fortschritt da, bevor man merkt, dass dieKomplexität der Domaine sich direkt im Code wiederspiegelt.

Nicht gleich alles komplett refactorn, sondern Step by Step.

Page 42: Domain Driven Design in Rails

Beispiel:Beispiel:

Page 43: Domain Driven Design in Rails
Page 44: Domain Driven Design in Rails
Page 45: Domain Driven Design in Rails