Domain DrivenDomain DrivenDesignDesign
in Ruby on Railsin Ruby on RailsCreated by Angelo Maron
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
AgendaAgenda1. MVC & Rails2. Domain Driven Design3. Domain Driven Design in Rails4. Zusammenfassung
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).
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
!Convention!!Convention!
MVC in Ruby on RailsMVC in Ruby on RailsM odel: ActiveRecord
V iew: ActionView
C ontroller: ActionController
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."
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
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
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.
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)
Domain-Driven Design inDomain-Driven Design inRailsRails
Rails HelpersConcernsService ObjectsPresenter ObjectsStrukturierte NamensräumeDecorator Pattern
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)
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>
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>
Domain-Driven Design inDomain-Driven Design inRailsRails
Rails HelpersConcernsService ObjectsPresenter ObjectsStrukturierte NamensräumeDecorator Pattern
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
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
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
Domain-Driven Design inDomain-Driven Design inRailsRails
Rails HelpersConcernsService ObjectsPresenter ObjectsStrukturierte NamensräumeDecorator Pattern
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)
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
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
Domain-Driven Design inDomain-Driven Design inRailsRails
Rails HelpersConcernsService ObjectsPresenter ObjectsStrukturierte NamensräumeDecorator Pattern
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.
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
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
Domain-Driven Design inDomain-Driven Design inRailsRails
Rails HelpersConcernsService ObjectsPresenter ObjectsStrukturierte NamensräumeDecorator Pattern
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.
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
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
Domain-Driven Design inDomain-Driven Design inRailsRails
Rails HelpersConcernsService ObjectsPresenter ObjectsStrukturierte NamensräumeDecorator Pattern
Decorators in Rails (1)Decorators in Rails (1)
Decorators in Rails (2)Decorators in Rails (2)
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>
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>
Und was nun?Und was nun?
Die alles entscheideneDie alles entscheideneFrageFrage
Welches Pattern benutzt man wann?Ab wann benutzen wir überhaupt diese Pattern?
NeuprogrammierungNeuprogrammierungAbwägung der Komplexität gegen den entstehenden Aufwand.Wichtig: Komplexe Business-Logik lässt sich nur durch komplexenCode abbilden.
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.
Beispiel:Beispiel:
Top Related