what follows? - jug saxony day 2018...example app issue-tracker $ rails generate scaffold issue...

Post on 03-Mar-2020

9 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

MVCWhat follows?

Daniel Grawunder / Christian Mierich

MVC-Frameworks

Laravel

Trygve Reenskaug

“Model–view–controller (MVC) is a software architectural pattern for implementing user interfaces.“

wikipedia

MODEL

CONTROLLERVIEW

USER

updates manipulates

usessees

Original-MVC

vs

Design Pattern

Architectural Pattern

Web-MVC

Example App Issue-Tracker

$ rails generate scaffold Issue title:string description:string state:integer

class IssuesController < ApplicationController

def index @issues = Issue.all end

def new @issue = Issue.new end

def show @issue = Issue.find(params[:id]) end

...

end

<h1>Listing Issues</h1><table> <thead> <tr> <td>Title</td> <td>Description</td> <td>State</td> </tr> </thead> <tbody> <% @issues.each do |issue| %> <tr> <td><%= issue.title %></td> <td><%= issue.description %></td> <td><%= issue.state %></td> </tr> <% end %> <tbody><table>

class Issue < ActiveRecord::Baseend

View Controller Model

creates

def index retrieve_query sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria) sort_update(@query.sortable_columns) @query.sort_criteria = sort_criteria.to_a

if @query.valid? case params[:format] when 'csv', 'pdf' @limit = Setting.issues_export_limit.to_i if params[:columns] == 'all' @query.column_names = @query.available_inline_columns.map(&:name) end when 'atom' @limit = Setting.feeds_limit.to_i when 'xml', 'json' @offset, @limit = api_offset_and_limit @query.column_names = %w(author) else @limit = per_page_option end

@issue_count = @query.issue_count @issue_pages = Paginator.new @issue_count, @limit, params['page'] @offset ||= @issue_pages.offset @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version], :order => sort_clause, :offset => @offset, :limit => @limit) @issue_count_by_group = @query.issue_count_by_group

...

Redmine Issues-Controller

Redmine - app/controllers/issues_controllers.rb

<div class="contextual"><% if !@query.new_record? && @query.editable_by?(User.current) %> <%= link_to l(:button_edit), edit_query_path(@query), :class => 'icon icon-edit' %><% end %></div>

<h2><%= @query.new_record? ? l(:label_issue_plural) : @query.name %></h2><% html_title(@query.new_record? ? l(:label_issue_plural) : @query.name) %><%= form_tag({ :controller => 'issues', :action => 'index', :project_id => @project }, :method => :get, :id => 'query_form') do %> <div id="query_form_with_buttons" class="hide-when-print"> <%= hidden_field_tag 'set_filter', '1' %> <div id="query_form_content"> ... </div> <p class="buttons">

... <% if @query.new_record? && User.current.allowed_to?(:save_queries, @project, :global => true) %> <%= link_to_function l(:button_save), "$('#query_form').attr('action', '#{ @project ? new_project_query_path(@project) : :class => 'icon icon-save' %> <% end %> </p> </div><% end %><%= error_messages_for 'query' %><% if @query.valid? %> <% if @issues.empty? %> <p class="nodata"><%= l(:label_no_data) %></p> <% else %> <%= render :partial => 'issues/list', :locals => {:issues => @issues, :query => @query} %>

Redmine Issue-Index-View

Redmine - app/view/issues/index.html.erb

class Issue < ActiveRecord::Base validates_length_of :subject, :maximum => 255 validates_inclusion_of :done_ratio, :in => 0..100 validates :estimated_hours, :numericality => {:greater_than_or_equal_to => 0, :allow_nil => true, :message => :invalid} validates :start_date, :date => true validates :due_date, :date => true validate :validate_issue, :validate_required_fields before_validation :clear_disabled_fields before_save :close_duplicates, :update_done_ratio_from_issue_status, :force_updated_on_change, :update_closed_on, :set_assigned_to_was

scope :recently_updated, lambda { order("#{Issue.table_name}.updated_on DESC") }

# Returns true if user or current user is allowed to view the issue def visible?(usr=nil) (usr || User.current).allowed_to?(:view_issues, self.project) do |role, user| if user.logged? case role.issues_visibility when 'all' true when 'default' !self.is_private? || (self.author == user || user.is_or_belongs_to?(assigned_to)) when 'own' self.author == user || user.is_or_belongs_to?(assigned_to) else false end else !self.is_private?

...

Redmine Issue-Model

Redmine - app/models/issues.rb

Web-MVC

CONTROLLERIssuesController

VIEWHTML-Template

MODELIssue

HTTP-Request

Bussiness logic Persistence logic (Queries)

UI logic

No Separation of Presentation

No Architecture

No Structure

OpenStreetMap

The heart of software is its ability to solve domain-related problems

for its user.All other features, vital though they may be, support this

basic purpose.Eric Evans, Domain-Driven DESIGN

Architecture The Lost YearsRobert C. Martin (Uncle Bob)

https://www.youtube.com/watch?v=WpkDN78P884#t=432

User

Presenter

View Model

View

Delivery M

echanism

Controller

(Use Case)Interactor

DomainEntity

Dat

a La

yer

Entity

Entity

EntityGateway

ImplementationEntity

Gateway

< I >

DatabaseAPI

< I >

Boundary

< I >

Boundary

Clean Architecture

User

Presenter

View Model

View

Delivery M

echanism

Controller

(Use Case)Interactor

DomainEntity

Entity

Entity

< I >

Boundary

< I >

Boundary

User

Presenter

View Model

View

Delivery M

echanism

Controller

Clean Architecture

< I >

Boundary

< I >

Boundary

Entity

Entity

Entity

Dat

a La

yer

EntityGateway

ImplementationEntity

Gateway

< I >

DatabaseAPI

Dat

a La

yer

EntityGateway

ImplementationEntity

Gateway

< I >

DatabaseAPI

User

Presenter

View Model

View

Delivery M

echanism

Controller

(Use Case)Interactor

DomainEntity

Entity

Entity

< I >

Boundary

< I >

Boundary

User

Presenter

View Model

View

Delivery M

echanism

Controller

Clean Architecture

< I >

Boundary

< I >

Boundary

Entity

Entity

Entity

Dat

a La

yer

EntityGateway

ImplementationEntity

Gateway

< I >

DatabaseAPI

Dat

a La

yer

EntityGateway

ImplementationEntity

Gateway

< I >

DatabaseAPI

Request Model

User

Presenter

View Model

View

Delivery M

echanism

Controller

(Use Case)Interactor

DomainEntity

Entity

Entity

< I >

Boundary

< I >

Boundary

User

Presenter

View Model

View

Delivery M

echanism

Controller

Clean Architecture

< I >

Boundary

< I >

Boundary

Entity

Entity

Entity

Dat

a La

yer

EntityGateway

ImplementationEntity

Gateway

< I >

DatabaseAPI

Dat

a La

yer

EntityGateway

ImplementationEntity

Gateway

< I >

DatabaseAPI

Request Model

User

Presenter

View Model

View

Delivery M

echanism

Controller

(Use Case)Interactor

DomainEntity

Entity

Entity

< I >

Boundary

< I >

Boundary

User

Presenter

View Model

View

Delivery M

echanism

Controller

Clean Architecture

< I >

Boundary

< I >

Boundary

Entity

Entity

Entity

Dat

a La

yer

EntityGateway

ImplementationEntity

Gateway

< I >

DatabaseAPI

Dat

a La

yer

EntityGateway

ImplementationEntity

Gateway

< I >

DatabaseAPI

Request Model

User

Presenter

View Model

View

Delivery M

echanism

Controller

(Use Case)

DomainEntity

Entity

Entity

< I >

Boundary

< I >

Boundary

User

Presenter

View Model

View

Delivery M

echanism

Controller

Clean Architecture

< I >

Boundary

< I >

Boundary

Entity

Entity

Entity

Dat

a La

yer

EntityGateway

ImplementationEntity

Gateway

< I >

DatabaseAPI

Dat

a La

yer

EntityGateway

ImplementationEntity

Gateway

< I >

DatabaseAPIInteractor

Result Model

User

Presenter

View Model

View

Delivery M

echanism

Controller

(Use Case)

DomainEntity

Entity

Entity

< I >

Boundary

< I >

Boundary

User

Presenter

View Model

View

Delivery M

echanism

Controller

Clean Architecture

< I >

Boundary

< I >

Boundary

Entity

Entity

Entity

Dat

a La

yer

EntityGateway

ImplementationEntity

Gateway

< I >

DatabaseAPI

Dat

a La

yer

EntityGateway

ImplementationEntity

Gateway

< I >

DatabaseAPIInteractor

Result Model

User

Presenter

View Model

View

Delivery M

echanism

Controller

(Use Case)Interactor

DomainEntity

Dat

a La

yer

Entity

Entity

EntityGateway

ImplementationEntity

Gateway

< I >

DatabaseAPI

< I >

Boundary

< I >

Boundary

Clean Architecture

A good architecture allows major decisions to be deferred

Robert C. Martin, Architecture the Lost Years

Domain

The “Rails” Way

Dat

a La

yer

User

Delivery M

echanism

View

ControllerModel

Model

Model

Model

Model

Necessary Validation

Rails propagatedValidation

Domain

The “Rails” Way

Dat

a La

yer

User

Delivery M

echanism

View

ControllerModel

Model

Model

Model

Model

Necessary Validation

Rails propagatedValidation

Rigidity

Fragility

Immobility

Our World with Web-MVC

Thinking in Layers

PRESENTATION

DOMAIN

DATA

Layers of a standard application

Delivery Mechanism

CONTROLLER

HTTP-Request

HTTP - Endpoint

VIEW MODEL

DOMAINDATA

PRESENTATION

Web-MVC

Delivery Mechanism

CONTROLLER

HTTP-Request

HTTP - Endpoint

VIEW MODEL

DOMAIN

QUERIES

DATA

Thinking in 3 Layers

PRESENTATION

Delivery Mechanism

CONTROLLER

HTTP-Request

HTTP - Endpoint

USE CASE

DOMAIN DATA

Clear Domain Boundaries

DOMAIN OBJECT

QUERIES

RECORD

PRESENTATION

VIEW

Delivery Mechanism

CONTROLLER

HTTP-Request

HTTP - Endpoint

USE CASE

DOMAIN DATA

Clear Domain Boundaries

DOMAIN OBJECT

QUERIES

RECORD

DOMAINOBJECTVIEW

PRESENTATION

Delivery Mechanism

CONTROLLER

HTTP-Request

HTTP - Endpoint

USE CASE

DOMAIN DATA

Using Presenters

DOMAIN OBJECT

QUERIES

RECORD

DOMAINOBJECT

PRESEN-TERVIEW

MVP/MVVM

PRESENTATION

Using Presenters

class IssuePresenter < DelegateClass(Issue)

def initialize issue, current_user super(issue) @issue = issue @current_user = current_user end

def show_edit_link? current_user.admin? || issue.user_id == current_user.id end

end

Presenters in Spring MVC

@Controller@RequestMappingpublic class IssuesController { @Autowired private IssueService issueService; @RequestMapping(method=RequestMethod.GET, value="/") public ModelAndView showAllIssues() { List<Issue> issues = issueService.findAll(); ModelAndView modelAndView = new ModelAndView("issues"); modelAndView.addObject("issues", issues);

return modelAndView; }}

Delivery Mechanism

CONTROLLER

HTTP-Request

HTTP - Endpoint

USE CASE

DOMAIN DATA

Web-App is not very special

DOMAIN OBJECT

QUERIES

RECORD

DOMAINOBJECT

PRESEN-TERVIEW

MVP/MVVM

PRESENTATION

Conclusion

Don’t repeat the same mistakes

Client-Side MVC

“Current JavaScript solutions suffer from "Double MVC". You need both server and client-side MVC stacks.”

David Heinemeier Hansson

Delivery Mechanism

CONTROLLER

HTTP-Request

HTTP - Endpoint

USE CASE

DOMAIN DATA

Server-Side

DOMAIN OBJECT

QUERIES

RECORD

DOMAINOBJECT

PRESEN-TERVIEW

PRESENTATION

Delivery Mechanism

CONTROLLER

HTTP-Request

HTTP - Endpoint

USE CASE

DOMAIN DATA

DOMAIN OBJECT

QUERIES

RECORD

DOMAINOBJECTSERIALIZER

PRESENTATION

Server-Side

Backbone.js

VIEWextends Backbone.

View

MODELextends Backbone.

Model

var MyView = Backbone.View.extends(...)var MyModel = Backbone.Model.extend(...)

var view = new MyView({model: new MyModel()});

(Controller)MV

PRESENTERextends Backbone.

Model

Backbone.js

VIEWextends Backbone.

View

MODELextends Backbone.

Model

var MyView = Backbone.View.extends(...)var MyModel = Backbone.Model.extend(...)var MyPresenter = Backbone.Model.extend(...)

var presenter = new MyPresenter({model: new MyModel()})var view = new MyView({model: presenter});

(Controller)MVP

CONTROLLERextends Ember.

Controller

Ember.js 2.0

COMPONENTextends Ember.

Component

MODELextends

DS.Model

TEMPLATEHANDLEBAR

(Presenter)

(VIEW)

(Controller)

MV-C/P

Questions?daniel.grawunder@objectfab.dechristian.mierich@objectfab.de

top related