Upload
others
View
0
Download
0
Embed Size (px)
Citation preview
23/03/2019 Shower Presentation Engine
localhost:8080/#54 1/64
Not the Rails Way
Игорь Александров, JetRockets Co-Founder
23/03/2019 Shower Presentation Engine
localhost:8080/#54 2/64
Игорь Александров
jetrockets.pro
github.com/jetrockets
github.com/igor-alexandrov
https://jetrockets.pro/https://github.com/jetrocketshttps://github.com/igor-alexandrov
23/03/2019 Shower Presentation Engine
localhost:8080/#54 3/64
23/03/2019 Shower Presentation Engine
localhost:8080/#54 4/64
23/03/2019 Shower Presentation Engine
localhost:8080/#54 5/64
23/03/2019 Shower Presentation Engine
localhost:8080/#54 6/64
23/03/2019 Shower Presentation Engine
localhost:8080/#54 7/64
23/03/2019 Shower Presentation Engine
localhost:8080/#54 8/64
23/03/2019 Shower Presentation Engine
localhost:8080/#54 9/64
Знакомо?
23/03/2019 Shower Presentation Engine
localhost:8080/#54 10/64
Разве проблема в Ruby?
23/03/2019 Shower Presentation Engine
localhost:8080/#54 11/64
Проблема №1 —архитектура
23/03/2019 Shower Presentation Engine
localhost:8080/#54 12/64
@document.save
class DocumentsController < ApplicationController
def create
@document = Document.new(params[:document])
# 90% of developers don't care about 'magic'
if
# …
else
# …
end
end
end
23/03/2019 Shower Presentation Engine
localhost:8080/#54 13/64
619
729
# app/models/documents.rb
# ###################################
620 # Callbacks
621
622 before_validation :on => :create do
623 self.scorer = applicant
624 self.closing_status ||= 'likely'
# …
728
# ###################################
730 # Class Methods
23/03/2019 Shower Presentation Engine
localhost:8080/#54 14/64
Приложение —цепочка сервисов
23/03/2019 Shower Presentation Engine
localhost:8080/#54 15/64
Запрос => [*] => [*] => Ответ
23/03/2019 Shower Presentation Engine
localhost:8080/#54 16/64
Приложение —цепочка функций
23/03/2019 Shower Presentation Engine
localhost:8080/#54 17/64
class CreateDocument
# ...
end
23/03/2019 Shower Presentation Engine
localhost:8080/#54 18/64
class CreateDocument
def call
# ...
end
end
23/03/2019 Shower Presentation Engine
localhost:8080/#54 19/64
class CreateDocument
def call(document, params)
# ...
end
end
23/03/2019 Shower Presentation Engine
localhost:8080/#54 20/64
class CreateDocument
def call(document, params)
document.attributes = params
document.save!
end
end
23/03/2019 Shower Presentation Engine
localhost:8080/#54 21/64
class CreateDocument
def call(document, params)
document.attributes = params
document.save!
end
end
23/03/2019 Shower Presentation Engine
localhost:8080/#54 22/64
class CreateDocument
def call(document, params)
document.attributes = params
# before_create
rename_document(document) if document.valid?
document.save!
end
end
23/03/2019 Shower Presentation Engine
localhost:8080/#54 23/64
class CreateDocument
def call(document, params)
document.attributes = params
rename_document(document) if document.valid?
document.save!
# after_commit on: :create
make_async_call_with_document(document)
end
end
23/03/2019 Shower Presentation Engine
localhost:8080/#54 24/64
Pros
Единый интерфейс сервисов;
частично избавились от колбеков;
тестирование функциональных объектов — проще;
композиция сервисов.
•
•
•
•
23/03/2019 Shower Presentation Engine
localhost:8080/#54 25/64
Cons
Валидации остались в моделях:
композиция сервисов получалась не всегда, получалосьдублирование;
тестирование не стало проще из-за невозможности управлятьзависимостями;
не получалось сделать композицию объектов (Domain).
•document.save(validate: false) document.from_crm = true
document.save
•
•
•
23/03/2019 Shower Presentation Engine
localhost:8080/#54 26/64
Silver Bullet?
Form Object!http://trailblazer.to/gems/reform
http://trailblazer.to/gems/reform
23/03/2019 Shower Presentation Engine
localhost:8080/#54 27/64
class CreateDocument
def call(document, params)
form = CreateDocumentForm.new(document)
if form.validate(params)
document = form.sync
rename_document(document)
end
document.save!
end
end
23/03/2019 Shower Presentation Engine
localhost:8080/#54 28/64
class CreateDocument
attr_reader :form_class
attr_reader :rename_document
def initialize(form_class:, rename_document:)
@form_class = form_class
@rename_document = rename_document
end
def call(document, params)
# …
end
end
23/03/2019 Shower Presentation Engine
localhost:8080/#54 29/64
class CreateDocument
def call(document, params)
form = form_class.new(document)
if form.validate(params)
document = form.sync
rename_document(document)
document.save!
end
document
end
end
23/03/2019 Shower Presentation Engine
localhost:8080/#54 30/64
dry-validationhttps://dry-rb.org/gems/dry-validation
https://dry-rb.org/gems/dry-validation
23/03/2019 Shower Presentation Engine
localhost:8080/#54 31/64
schema
# app/services/document/create_document.rb
class Document::CreateDocument
def call(document, params)
result = (document).(params)
if result.success?
# …
end
end
end
class CreateDocumentSchema < Dry::Validation::Schema
end
23/03/2019 Shower Presentation Engine
localhost:8080/#54 32/64
# Initialize once
command = CreateDocument.new(
rename_document: Document::RenameDocument.new
)
# Use several times
command.call(passport, params[:passport])
command.call(visa, params[:visa])
23/03/2019 Shower Presentation Engine
localhost:8080/#54 33/64
Profit?
23/03/2019 Shower Presentation Engine
localhost:8080/#54 34/64
Не совсем…
23/03/2019 Shower Presentation Engine
localhost:8080/#54 35/64
class Document::CreateDocument
attr_reader :rename_document
attr_reader :upload_document_to_perfect_audit
attr_reader :update_expiration_dates
attr_reader :convert_document_to_pdf
attr_reader :create_affiliations
attr_reader :update_cpl_records
attr_reader :update_application_status
# hundreds of other stuff here
# …
end
23/03/2019 Shower Presentation Engine
localhost:8080/#54 36/64
Silver Bullet?
dry-containerhttps://dry-rb.org/gems/dry-container
https://dry-rb.org/gems/dry-container
23/03/2019 Shower Presentation Engine
localhost:8080/#54 37/64
dry-container
IoC Container
Stubs!
Thread safe
•
•
•
23/03/2019 Shower Presentation Engine
localhost:8080/#54 38/64
# app/containers/global_container.rb
module GlobalContainer
extend Dry::Container::Mixin
namespace('services') do
register('create_document') do
Document::CreateDocument.new(
rename_document: self['services.rename_document']
)
end
register('rename_document') do
Document::RenameDocument.new
end
end
end
23/03/2019 Shower Presentation Engine
localhost:8080/#54 39/64
# создание документа от заёмщика command = GlobalContainer['services.create_document']
command.rename_document # Document::RenameDocument
command.class # Document::CreateDocument
# создания документа из CRM command = GlobalContainer['crm.services.create_document']
command.rename_document # Crm::Document::RenameDocument
command.class # Document::CreateDocument
23/03/2019 Shower Presentation Engine
localhost:8080/#54 40/64
Profit?
23/03/2019 Shower Presentation Engine
localhost:8080/#54 41/64
2046
# app/lib/containers/global_container.rb
1 module GlobalContainer
2 extend Dry::Container::Mixin
namespace('services') do
# …
2044 end
2045 end
end
2046 LOC???
IOC головного мозга
23/03/2019 Shower Presentation Engine
localhost:8080/#54 42/64
# app/lib/containers/api_v2_container.rb
1 module ApiV2Container
2 extend Dry::Container::Mixin
namespace('auth') do
# ...
124 end
125 end
126 end
23/03/2019 Shower Presentation Engine
localhost:8080/#54 43/64
# app/services/document/create_document.rb
class Document::CreateDocument
def call(document, params)
result = schema(document).(params)
if result.success?
# …
else
return false
end
end
end
23/03/2019 Shower Presentation Engine
localhost:8080/#54 44/64
return false
# app/services/document/create_document.rb
class Document::CreateDocument
def call(document, params)
result = schema(document).(params)
if result.success?
# …
else
end
end
end
23/03/2019 Shower Presentation Engine
localhost:8080/#54 45/64
Silver Bullet?
dry-monadshttps://dry-rb.org/gems/dry-monads
https://dry-rb.org/gems/dry-monads
23/03/2019 Shower Presentation Engine
localhost:8080/#54 46/64
dry-monads
Монада – это то, что все знают, но не могут объяснить;
монада – контейнер, в котором есть результат с объектом;
chaining;
синтаксический сахар.
•
•
•
•
23/03/2019 Shower Presentation Engine
localhost:8080/#54 47/64
class CreateDocument
def call(document, params)
result = schema(document).(params)
if result.success?
# …
Success(document)
else
Failure(result.errors)
end
end
end
23/03/2019 Shower Presentation Engine
localhost:8080/#54 48/64
result = command.call(document, params[:document])
case result
when Dry::Monads::Success
# …
when Dry::Monads.Failure(LendingOne::ValidationError)
# …
when Dry::Monads::Failure
# …
end
23/03/2019 Shower Presentation Engine
localhost:8080/#54 49/64
Pros
Тестирование функциональных объектов;
простое расширение логики;
композиция – основа моделирования процессов;
масштабирование и слабая связанность компонентовприложения.
•
•
•
•
23/03/2019 Shower Presentation Engine
localhost:8080/#54 50/64
Cons
Имитация функций объектами;
концептуально множатся сущности (которые не являютсясущностями по факту);
IoC с доступом по строкам;
можно написать монадический метод и вернуть немонадическое значение;
•
•
•
•
23/03/2019 Shower Presentation Engine
localhost:8080/#54 51/64
Проблема №2.
Nobody wants to think hack
23/03/2019 Shower Presentation Engine
localhost:8080/#54 52/64
ActiveSupport
number_to_currency
23/03/2019 Shower Presentation Engine
localhost:8080/#54 53/64
number_to_currency
report = MemoryProfiler.report do
# ActiveSupport::NumberHelper::NumberToCurrencyConverter
number_to_currency(BigDecimal(1543679.753))
end
report.total_allocated_memsize
# 10168
23/03/2019 Shower Presentation Engine
localhost:8080/#54 54/64
10 килобайт?
23/03/2019 Shower Presentation Engine
localhost:8080/#54 55/64
FasterSupporthttps://github.com/jetrockets/faster_support
https://github.com/jetrockets/faster_support
23/03/2019 Shower Presentation Engine
localhost:8080/#54 56/64
FasterSupport
report = MemoryProfiler.report do
d = BigDecimal(1543679.753)
FasterSupport::Numbers.number_to_currency_n_u(d)
end
report.total_allocated_memsize
# 80
23/03/2019 Shower Presentation Engine
localhost:8080/#54 57/64
Performance
Checking for BigDecimal
Warming up --------------------------------------
ActiveSupport 874.000 i/100ms
FasterSupport 30.613k i/100ms
Calculating -------------------------------------
ActiveSupport 9.263k (± 5.2%) i/s - 46.322k in 5.015584s
FasterSupport 386.647k (± 2.9%) i/s - 1.959M in 5.071773s
Comparison:
FasterSupport: 386647.3 i/s
ActiveSupport: 9262.8 i/s - 41.74x slower
23/03/2019 Shower Presentation Engine
localhost:8080/#54 58/64
Counter Cache
d = Document.first
d.comments_count # => 0
Document.increment_counter(:comments_count, 1)
d.comments_count # => 0
23/03/2019 Shower Presentation Engine
localhost:8080/#54 59/64
Counter Cache
d = Document.first
d.comments_count # => 0
d.increment(:comments_count)
d.reload
d.comments_count # => 0
23/03/2019 Shower Presentation Engine
localhost:8080/#54 60/64
PostgreSQL
RETURNINGstatement
23/03/2019 Shower Presentation Engine
localhost:8080/#54 61/64
RETURNING
RETURNING statement
UPDATE "documents"
SET
"comments_count" = COALESCE("comments_count", 0) + 1
WHERE
"documents"."id" = 343195
"id", "comments_count"
23/03/2019 Shower Presentation Engine
localhost:8080/#54 62/64
RETURNING statement
d = Document.first
d.comments_count # => 0
d.increment_counter_with_value(:comments_count)
d.comments_count # => 1
23/03/2019 Shower Presentation Engine
localhost:8080/#54 63/64
PostgreSQL RETURNING statement
activerecord-update_counters_with_values
https://github.com/jetrockets/activerecord-update_counters_with_values
23/03/2019 Shower Presentation Engine
localhost:8080/#54 64/64
Links
jetrockets.pro
github.com/jetrockets
github.com/igor-alexandrov
https://jetrockets.pro/https://github.com/jetrocketshttps://github.com/igor-alexandrov