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
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
MVC in Ruby on Rails
M odel: ActiveRecord
V iew: 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)
end
def index
@published_posts = Post.all_published
@unpublished_posts = Post.all_unpublished
end
Problematik
Mit zunehmenden Anforderungen werden können die folgenden
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 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 Design (DDD) ist eine
Herangehensweise an die Modellierung
komplexer objektorientierter Software. Die
Modellierung der Software wird dabei
maßgeblich von den umzusetzenden
Fachlichkeiten der Anwendungsdomäne
beeinflusst.
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 wiederholende Funktionen wieder zu verwenden.
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')) %>
<%- end %>
</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')
end
end
end
<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 folgende Anwendungsmöglichkeiten:
1. Aufteilen des Codes eines Models in mehrere Dateien
2. Auslagerung von Mechanismen/Pattern zur Wiederverwendung
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'
end
class Job < ActiveRecord::Base
belongs_to :create_user, class_name: 'User'
belongs_to :update_user, class_name: 'User'
end
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'
end
end
class Article < ActiveRecord::Base
include Trackable
end
class Job < ActiveRecord::Base
include Trackable
end
Domain-Driven Design in
Rails
Rails Helpers
Concerns
Service Objects
Presenter Objects
Strukturierte Namensräume
Decorator Pattern
Service Objects
Verlagerung 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)
class Article < ActiveRecord::Base
def search(term, options = {})
# execute search
end
def admin_search(term, options = {})
# execute search from admin perspective
end
end
def Job < ActiveRecord::Base
def search(term, options = {})
# execute search
end
def admin_search(term, options = {})
# execute search from admin perspective
end
end
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
end
end
class AdminSearchService
def self.article_search(term, options = {})
# execute search here
end
def self.job_search(term, options = {})
# execute search here
end
end
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])
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)
end
end
Beispiel Presenter Object
(2)
class JobController < ApplicationController::Base
def index
@jobs = JobPresenter.get_all(params)
end
end
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])
e n d
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 folgende 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
end
def self.all_for_moderators
# execute query here
end
def self.all_for_visitors
#execute query here
end
end
Beispiel Namensrääume (2)
module Admins
class Job
def self.all
#execute query here
end
end
end
module Moderators
class Job
def self.all
#execute query here
end
end
end
module Visitors
class Job
def self.all
#execute query here
end
end
end
Domain-Driven Design in
Rails
Rails Helpers
Concerns
Service Objects
Presenter Objects
Strukturierte Namensräume
Decorator Pattern
Beispiel Decorator (1)
class UserController < ApplicationController::Base
def show
@user = User.find(params[:id])
end
end
<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)
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
end
end
class UserController < ApplicationController::Base
def show
@user_decorator = UserDecorator.new(User.find(params[:id]))
end
end
<div>
<p>Erstellt am: <%= @user_decorator.registered_at</p>
Die alles entscheidene
Frage
Welches Pattern benutzt man wann?
Ab wann benutzen wir überhaupt diese Pattern?
Neuprogrammierung
Abwägung der Komplexität gegen den entstehenden 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.