Rails goes SOA

Preview:

DESCRIPTION

The slides of my talk at Rails-Konferenz 2009 in Frankfurt/Offenbach about bringing together the ideas of SOA with Rails to handle complexity.

Citation preview

Rails goes SOARails-Konferenz 2009 – Frankfurt/Offenbach

Dirk Breuer – pkw.de

02. September 2009

View

Model(enthält Business Logik + Daten)

Controller (nur lesend)

View

Model(enthält Business Logik)

Controller

BusinessLogik

(nur lesend)

View

Model(Datenhaltung)

Controller(nur lesend)Business

Logikextract

Vorteile der Transformation

Entwickler erhalten Kontrolle über Source Code zurück

Bessere Verständlichkeit für die Fachseite

Grafik © by Martin Laksman, 2003

Vorteile der Transformation

Entwickler erhalten Kontrolle über Source Code zurück

Bessere Verständlichkeit für die Fachseite

Bessere Time-to-Market

Grafik © by Martin Laksman, 2003

Service-Orientierte Architekturen

View

Model(Datenhaltung)

Controller(nur lesend)Services

(enthalten Business Logik)

How To SOA?

Fahrzeuge Suchen

Fahrzeuge fürdie Startseite

anzeigen

Suchauftrag fürSuche anlegen

Benutzerauthentifizieren

Statistiken fürein Fahrzeug

anzeigen

Statistiken fürein Fahrzeug

anzeigen

StatistikenSchreiben

get_startpage_carsget_startpage_dealerget_startpage_teasersearch_carscreate_temporary_customercreate_temporary_carsave_temporary_customersave_temporary_carauthenticate_customeradd_image_to_cargenerate_captchaverify_captchaaccept_customerpublish_carnotify_customer_about_published_car

get_statistics_of_carget_number_of_cars_of_customerget_cars_of_customerget_number_of_car_imagesget_car_imagesdelete_cardelete_customerget_search_orders_of_customerdelete_search_ordersearch_cars_by_search_orderauthenticate_customerauthenticate_dealergenerate_passwordsend_new_password…

Vorteile von SOA

Services werden von Fachseite gefunden

Fachseite versteht IT besser

Fachseite versteht Komplexität besser

Fachliche Sicht sehr langlebig, dadurch Services sehr langlebig

Vorteile von SOA

Services werden von Fachseite gefunden

Fachseite versteht IT besser

Fachseite versteht Komplexität besser

Fachliche Sicht sehr langlebig, dadurch Services sehr langlebig

Langlebigkeit ...?

Gra

fik ©

by

Mar

tin L

aksm

an, 2

003

Langlebigkeit der Datenbank?

Änderung von Spalten

Änderung des CRM-Systems

Anbindung an SAP

Zusammenlegung von StartUps

Langlebigkeit der GUI?

Änderung der Anordnung von Elementen

Änderung von Dialogen

Änderung von Texten

Redesign der gesamten Plattform

Langlebigkeit von Services

Fachseite definiert Geschäftsprozesse

Fahrzeuge suchen

Benutzer authentifizieren

Statistiken schreiben

Änderungen auf dieser Ebene sehr viel unwahrscheinlicher

Und was genau ist ein Service Jetzt?!

Grafik © by Martin Laksman, 2003

Ein Service ist …

… technologie- und plattformunabhängig

… dynamische lokalisier- und ausführbar

… in sich abgeschlossen

… durch eine wohl-definierte und formale Schnittstelle beschrieben

… stateless

Dienstbeschreibungveröffentlichen

Dienstimplementierungbinden

Dienstbeschreibungfinden

Service Registry

Service Provider

Service Client

Basic SOA

Aber kosten diese SOA Lösungen von IBM & Co nicht

sehr viel Geld?

Grafik © by Martin Laksman, 2003

Ja ...

Aber kosten diese SOA Lösungen von IBM & Co nicht

sehr viel Geld?

Grafik © by Martin Laksman, 2003

Das können wir uns nicht leisten!

Ja ...

Aber kosten diese SOA Lösungen von IBM & Co nicht

sehr viel Geld?

Grafik © by Martin Laksman, 2003

SOA ist doch nur ein Konzept. Das können

wir auch selbst umsetzen.

Grafik © by Martin Laksman, 2003

SOA ist doch nur ein Konzept. Das können

wir auch selbst umsetzen.

Und zwar passendfür uns!

Grafik © by Martin Laksman, 2003

+ SOA

class SearchService

def self.search_cars(search_criteria) # Calling the Car class which is just # a ActiveRecord::Base subclass end

end

found_cars = SearchService.search_cars(some_criteria)

class SearchService

def self.search_cars(search_criteria) # Calling the Car class which is just # a ActiveRecord::Base subclass end

end

found_cars = SearchService.search_cars(some_criteria)

Aber moment, das sieht

class Car < ActiveRecord::Base

def self.search(criteria) find(:all, :conditition => criteria) end

end

found_cars = Car.search(params[:criteria])

class Car < ActiveRecord::Base

def self.search(criteria) find(:all, :conditition => criteria) end

end

found_cars = Car.search(params[:criteria])Aber ...

Echte Businesslogik ist in der Regel deutlich komplexer

Bisher: ActiveRecord::Base = Businesslogik + Daten

Jetzt: ActiveRecord::Base = Daten, Service = Businesslogik

Echte Businesslogik ist in der Regel deutlich komplexer

Bisher: ActiveRecord::Base = Businesslogik + Daten

Jetzt: ActiveRecord::Base = Daten, Service = Businesslogik

inkl. einfacher Validierung etc.

Verteilung

Statistic Service

Such Service

SuchauftragService

AuthentifizierungsService

RailsFrontend

Verteilung

Statistic Service

Such Service

SuchauftragService

AuthentifizierungsService

RailsFrontend

?

?

?

Verteilung

Statistic Service

Such Service

SuchauftragService

AuthentifizierungsService

RailsFrontend

?

?

?Thrift-RPC

Thrift-RPC?!?

Grafik © by Martin Laksman, 2003

Thrift-RPC

RPC-Framework von Facebook entwickelt

Mittlerweile Apache Incubator Projekt: http://incubator.apache.org/thrift/

Ziel: Verlässliche und effiziente Kommunikation verteilter Systemkomponenten

Thrift-Architektur

Transport

Protocol

Processor

Service-Implementierung

Thrift-Architektur

Transport

Protocol

Processor

Service-Implementierung

Thrift-Compiler

Thrift-Gem

Thrift ServiceDefinitionerstellen

Thrift ServiceDefinitionerstellen

Service Stubsgenerieren

Thrift ServiceDefinitionerstellen

Service Stubsgenerieren

Serviceimplementieren

Thrift ServiceDefinitionerstellen

Transport-Schicht

realisierenService Stubsgenerieren

Serviceimplementieren

Thrift ServiceDefinitionerstellen

Transport-Schicht

realisierenService Stubsgenerieren

Serviceimplementieren

Serviceansprechen

Thrift ServiceDefinitionerstellen

Transport-Schicht

realisierenService Stubsgenerieren

Serviceimplementieren

Serviceansprechen

Thrift Service-Definition

namespace rb UserService

enum UserStates { VERIFIED = 1, UNVERIFIED = 2, BLOCKED = 3}

struct User { 1: string firstname, 2: string lastname, 3: i32 age, 4: string email, 5: string username, 6: UserStates state}

exception AuthenticationError { 1: string message, 2: list<string> backtrace}

service AuthenticationService { User authenticate_user(1:string username, 2:string password)throws (1:AuthenticationError ae)}

Thrift Service-Definition

✓Namespacesnamespace rb UserService

enum UserStates { VERIFIED = 1, UNVERIFIED = 2, BLOCKED = 3}

struct User { 1: string firstname, 2: string lastname, 3: i32 age, 4: string email, 5: string username, 6: UserStates state}

exception AuthenticationError { 1: string message, 2: list<string> backtrace}

service AuthenticationService { User authenticate_user(1:string username, 2:string password)throws (1:AuthenticationError ae)}

Thrift Service-Definition

✓Namespaces

✓Enums

namespace rb UserService

enum UserStates { VERIFIED = 1, UNVERIFIED = 2, BLOCKED = 3}

struct User { 1: string firstname, 2: string lastname, 3: i32 age, 4: string email, 5: string username, 6: UserStates state}

exception AuthenticationError { 1: string message, 2: list<string> backtrace}

service AuthenticationService { User authenticate_user(1:string username, 2:string password)throws (1:AuthenticationError ae)}

Thrift Service-Definition

✓Namespaces

✓Enums

✓Primitive Datentypen

namespace rb UserService

enum UserStates { VERIFIED = 1, UNVERIFIED = 2, BLOCKED = 3}

struct User { 1: string firstname, 2: string lastname, 3: i32 age, 4: string email, 5: string username, 6: UserStates state}

exception AuthenticationError { 1: string message, 2: list<string> backtrace}

service AuthenticationService { User authenticate_user(1:string username, 2:string password)throws (1:AuthenticationError ae)}

Thrift Service-Definition

✓Namespaces

✓Enums

✓Primitive Datentypen

✓Komplexe Typen

namespace rb UserService

enum UserStates { VERIFIED = 1, UNVERIFIED = 2, BLOCKED = 3}

struct User { 1: string firstname, 2: string lastname, 3: i32 age, 4: string email, 5: string username, 6: UserStates state}

exception AuthenticationError { 1: string message, 2: list<string> backtrace}

service AuthenticationService { User authenticate_user(1:string username, 2:string password)throws (1:AuthenticationError ae)}

Thrift Service-Definition

✓Namespaces

✓Enums

✓Primitive Datentypen

✓Komplexe Typen

✓Exceptions

namespace rb UserService

enum UserStates { VERIFIED = 1, UNVERIFIED = 2, BLOCKED = 3}

struct User { 1: string firstname, 2: string lastname, 3: i32 age, 4: string email, 5: string username, 6: UserStates state}

exception AuthenticationError { 1: string message, 2: list<string> backtrace}

service AuthenticationService { User authenticate_user(1:string username, 2:string password)throws (1:AuthenticationError ae)}

Thrift Service-Definition

✓Namespaces

✓Enums

✓Primitive Datentypen

✓Komplexe Typen

✓Exceptions

✓Services

namespace rb UserService

enum UserStates { VERIFIED = 1, UNVERIFIED = 2, BLOCKED = 3}

struct User { 1: string firstname, 2: string lastname, 3: i32 age, 4: string email, 5: string username, 6: UserStates state}

exception AuthenticationError { 1: string message, 2: list<string> backtrace}

service AuthenticationService { User authenticate_user(1:string username, 2:string password)throws (1:AuthenticationError ae)}

Walkthrough: Statistik-Service

namespace rb Pkwde.StatisticsModule

struct Statistic { 1:string name, 2:i32 count}

service StatisticsService { list<Statistic> statistic_for(1:list<string> element_names), oneway void increment(1:list<string> elements)}

$> thrift --gen rb:rails -o ./app/thrift config/statistics_service.thrift

app/thrift`-- gen-rb `-- pkwde `-- statistics_module |-- statistics_service.rb |-- statistics_service_constants.rb `-- statistics_service_types.rb

class StatisticsHandler def statistic_for(element_names) Statistic.find_all_by_name(element_names).map do |statistic| Pkwde::StatisticsModule::Statistic.new(:name => statistic.name, :count => statistic.count) end end def increment(elements) elements.each do |element| Statistic.find_or_create_by_name(element).increment!(:count) end end end

require 'thrift/server/rack_middleware'

ActionController::Dispatcher.middleware.insert_before(Rails::Rack::Metal, Thrift::RackMiddleware, { :processor => Pkwde::StatisticsModule::StatisticsService::Processor.new(StatisticsHandler.new), :hook_path => "/statistics" })

Service Provider

StatisticsHandler.new

require 'thrift/server/rack_middleware'

ActionController::Dispatcher.middleware.insert_before(Rails::Rack::Metal, Thrift::RackMiddleware, { :processor => Pkwde::StatisticsModule::StatisticsService::Processor.new(StatisticsHandler.new), :hook_path => "/statistics" })

Service Provider

StatisticsHandler.new

require 'thrift/server/rack_middleware'

ActionController::Dispatcher.middleware.insert_before(Rails::Rack::Metal, Thrift::RackMiddleware, { :processor => Pkwde::StatisticsModule::StatisticsService::Processor.new(StatisticsHandler.new), :hook_path => "/statistics" })

statistics_client = ThriftClient.new(:url => "http://localhost:3000/statistics", :client_class => Pkwde::StatisticsModule::StatisticsService::Client)

Service Provider

Service Client

StatisticsHandler.new

require 'thrift/server/rack_middleware'

ActionController::Dispatcher.middleware.insert_before(Rails::Rack::Metal, Thrift::RackMiddleware, { :processor => Pkwde::StatisticsModule::StatisticsService::Processor.new(StatisticsHandler.new), :hook_path => "/statistics" })

statistics_client = ThriftClient.new(:url => "http://localhost:3000/statistics", :client_class => Pkwde::StatisticsModule::StatisticsService::Client)

Service Provider

Service Client

http://github.com/railsbros/thrift4rails

http://github.com/railsbros/thrift

StatisticsHandler.new

Was ist Eigentlich mit einer Lösung die mehr

Ruby ist …?

Grafik © by Martin Laksman, 2003

Hoth::Services.define do service :increment_statistics, :params => [[:element]], :returns => nil, :endpoint => :statistics_module service :statistic_for, :params => [[:element_name]], :returns => [:statistic], :endpoint => :statistics_module end

Sneak Preview

Service-Definition

Hoth::ServiceDeployment.define do service_module :statistics_module do env :test, { :endpoint => Hoth::Endpoint.new(:host => 'localhost', :port => 3000), :mongrel_servers => 2, :mongrel_start_port => 9001 }

path "services/search_service" endend

Sneak Preview

Deployment-Definition

ENV["LOCAL"] = "false"

require 'hoth'require 'deployment_definition'require 'service_definition'

Hoth::Services.increment_statistics([statistic_object], event)Hoth::Services.statistic_of_cars([23]).inspect

Sneak Preview

Service-Client

ENV["LOCAL"] = "true"

require 'hoth'require 'deployment_definition'require 'service_definition'

class IncrementStatisticsImpl def self.execute(elements) elements.each do |element| Statistic.find_or_create_by_name(element).increment!(:count) end endend

class StatisticForImpl def self.execute(element_names) Statistic.find_all_by_name(element_names) endend

app = lambda {|env| [200, {'Content-Type' => 'text/plain'}, ""]}run Hoth::ServiceProvider.new(app)

Service-Provider

About Me …

Rubyist und Rails-Enthusiast seit Ende 2005

Software-Entwickler bei pkw.de seit 2007

Twitter: @railsbros_dirk

GitHub: http://github.com/pkwde

Blog: http://railsbros.de

Kann ich das gleiche nicht durch ein

gutes OO-Design realisieren?

Grafik © by Martin Laksman, 2003

SOA vs. OOP

SOA ersetzt OOP nicht!

SOA setzt auf einer anderen Ebene an

OOP Design Prinzipien finden jedoch keine Anwendung für SOA

Fokus auf die Abbildung von Geschäftsprozessen

Ein Service kann intern immer noch OOP sein

Recommended