Domain Driven Design in Rails

  • View
    225

  • Download
    67

  • Category

    Software

Preview:

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

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:

Recommended