Domain Driven Design in Ruby on Rails Created by Angelo Maron
Wer bin ich? Angelo Maron Sofware-Entwickler seit ca. 7 Jahren (Ruby on Rails) bei AKRA seit 2,5 Jahren Xing: https://www.xing.com/profile/angelo_maron Twitter: @MrRaffnix
Aga 1. MVC & Rails 2. Domain Driven Design 3. Domain Driven Design in Rails 4. Zusammenfassung
MVC (Model View Controller) Wikipedia: Der englischsprachige Begriff "model view controller" (MVC) ist ein Muster zur Strukturierung von Software-Entwicklung in die drei Einheiten Datenmodell (engl. model), Präsentation (engl. view) und Programmsteuerung (engl. controller).
Ruby on Rails Ruby on Rails ist ein in der Programmiersprache Ruby geschriebenes und quelloffenes Web Application Framework. wird dieses Jahr 10 Jahre alt. basiert auf dem MVC-Pattern Prinzipien: Don't repeat yourself (DRY) Convention over Configuration
!Convention!
MVC in Ruby on Rails M odel: V iew: ActiveRecord ActionView C ontroller: ActionController
Fat Model, Skinny Con- troller (1) "Im Zusammenspiel von Controller und Model, versuche im Controller nur das Objekt zu instanzieren und einen Methodenaufruf durchzuführen. Der Rest wird im Model definiert."
Fat Model, Skinny Con- 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) def index @published_posts = Post.all_published @unpublished_posts = Post.all_unpublished
Problematik Mit zunehmen Anforderungen werden können die folgen Problematiken 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 und Verständnis des Codes. Daher habe ich versucht nach den folgen Regeln zu arbeiten. (nach Sandy Matz) --> Domain Driven Design Eine Klasse hat maximal 100 Zeilen. Eine Methode hat maximal 5 Zeilen.
Domain Driven Design Domain-Driven Design (DDD) ist eine Herangehensweise an die Modellierung komplexer objektorientierter Software. Die Modellierung der Software wird dabei maßgeblich von den umzusetzen Fachlichkeiten der Anwungsdomäne beeinflusst.
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 in Rails Rails Helpers Concerns Service Objects Presenter Objects Strukturierte Namensräume Decorator Pattern
Rails Helpers Interne Struktur von Rails um Funktionen für views zur Verfügung zu stellen. Funktionen um komplexen Code aus der View zu halten. Sich wiederhole Funktionen wieder zu verwen. Persönlich: (erinnert mich an funktionale Programmierung)
Beispiel Rails Helper (1) <div> <%- if @user.image.present? %> <%= image_tag(@user.image.path) %> <%- else %> <%= image_tag(asset_path('default_profile.png')) %> <%- %> </div>
Beispiel Rails Helper (2) module UserHelper def user_picture_path(user) if user.image.present? user.image.path else asset_path('default_profile.png') <div> <%= image_tag(user_picture_path(@user)) %> </div>
Domain-Driven Design in Rails Rails Helpers Concerns Service Objects Presenter Objects Strukturierte Namensräume Decorator Pattern
Rails Concerns Rails Concerns ist eine Möglichkeit Code in Module auszulagern, die man über ein include in ein beliebiges Model inkludieren kann. Dieses hat folge Anwungsmöglichkeiten: 1. Aufteilen des Codes eines Models in mehrere Dateien 2. Auslagerung von Mechanismen/Pattern zur Wiederverwung Rails bietet hier ein hauseigenes Konzept: ActiveSupport::Concern
Beispiel Rails Concerns (1) class Article < ActiveRecord::Base belongs_to :create_user, class_name: 'User' belongs_to :update_user, class_name: 'User' class Job < ActiveRecord::Base belongs_to :create_user, class_name: 'User' belongs_to :update_user, class_name: 'User'
Beispiel Rails Concerns (2) module Trackable ext ActiveSupport::Concern included do belongs_to :create_user, class_name: 'User' belongs_to :update_user, class_name: 'User' class Article < ActiveRecord::Base include Trackable class Job < ActiveRecord::Base include Trackable
Domain-Driven Design in Rails Rails Helpers Concerns Service Objects Presenter Objects Strukturierte Namensräume Decorator Pattern
Service Objects Verlagerung bestimmter Funktionen eines Anwungsfall in ein eigenes Objekt, dass genau diese Funktionen zur Verfügung stellt. zum Beispiel: SearchService (Suchkomponente) InvoiceService (Rechnungserstellung) RegistrationService (RegistrierungsValidierung)
Beispiel Service Object (1) class Article < ActiveRecord::Base def search(term, options = {}) # execute search def admin_search(term, options = {}) # execute search from admin perspective def Job < ActiveRecord::Base def search(term, options = {}) # execute search def admin_search(term, options = {}) # execute search from admin perspective
Beispiel Service Object (2) class SearchService def self.article_search(term, options = {}) # execute search here def self.job_search(term, options = {}) # execute search here class AdminSearchService def self.article_search(term, options = {}) # execute search here def self.job_search(term, options = {}) # execute search here
Domain-Driven Design in Rails Rails Helpers Concerns Service Objects Presenter Objects Strukturierte Namensräume Decorator Pattern
Presenter Objects Oft haben Get-Operationen komplexe Analyse von Parametern, um die genau angeforderten Objekte anzuzeigen. Hier gibt es eine gute Möglichkeit, diese in eigene Objekte auszulagern.
Beispiel Presenter Object (1) class JobController < ApplicationController::Base def index @jobs = Job.active.preload(:items) if params[:state] @jobs = @jobs.by_state(params[:state]) if params[:order] order_string = "#{params[:order]} #{params[:dir].presence 'asc'}" @jobs = @jobs.order(order_string) @jobs = @jobs.page(params[:page].presence 1).per(params[:per].presence 10)
Beispiel Presenter Object (2) class JobController < ApplicationController::Base def index @jobs = JobPresenter.get_all(params) class JobPresenter def self.get_all(params) self.new(params).get_all def new(params) @params = params def get_all jobs = Job.active.preload(:items) if @params[:state] jobs = jobs.by_state(@params[:state])
Domain-Driven Design in Rails Rails Helpers Concerns Service Objects Presenter Objects Strukturierte Namensräume Decorator Pattern
Strukturierte Namen- sräume Strukturierung aller Objekte (Models, Services, Presenters, Decorators, Controllers usw.) in Domain-basierte Namespaces. Dies hat folge Vorteile: Code-Unabhängigkeit kleinere Dateien bessere Abbildung der Businessdomäne im Code.
Beispiel Namensräume (1) class Job < ActiveRecord::Base def self.all_for_admins #execute query here def self.all_for_moderators # execute query here def self.all_for_visitors #execute query here
Beispiel Namensräume (2) module Admins class Job def self.all #execute query here module Moderators class Job def self.all #execute query here module Visitors class Job def self.all #execute query here
Domain-Driven Design in Rails Rails Helpers Concerns Service Objects Presenter Objects Strukturierte Namensräume Decorator Pattern
Decorators in Rails (1)
Decorators in Rails (2)
Beispiel Decorator (1) class UserController < ApplicationController::Base def show @user = User.find(params[:id]) <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')) %> <%- %> </div>
Beispiel Decorator (2) class UserDecorator < SimpleDelegator def registered_at @object.registered_at.strftime('%y%m%d') def name "#{@object.first_name} #{@object.last_name}" def image_path # return image.path or default class UserController < ApplicationController::Base def show @user_decorator = UserDecorator.new(User.find(params[:id])) <div> <p>erstellt am: <%= @user_decorator.registered_at</p>
Und was nun?
Die alles entscheidene Frage Welches Pattern benutzt man wann? Ab wann benutzen wir überhaupt diese Pattern?
Neuprogrammierung Abwägung der Komplexität gegen den entstehen Aufwand. Wichtig: Komplexe Business-Logik lässt sich nur durch komplexen Code abbilden.
Refactoring Oft ist schon ein gewisser Fortschritt da, bevor man merkt, dass die Komplexität der Domaine sich direkt im Code wiederspiegelt. Nicht gleich alles komplett refactorn, sondern Step by Step.
Beispiel: