Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Hanami - нова надія Ruby чи "імперія ходить по тим самим граблям"?

Hanami - нова надія Ruby чи "імперія ходить по тим самим граблям"?

Yevhen "Eugene" Kuzminov

November 01, 2018
Tweet

More Decks by Yevhen "Eugene" Kuzminov

Other Decks in Programming

Transcript

  1. Hanami - нова надія Ruby чи "імперія ходить по тим

    самим граблям"? Євген Кузьмінов Ruby Team Lead
  2. Хто я • 10 років у веб розробці • 6

    роки у PHP • 4 роки у Ruby • з них 3 роки по коліно у Trailblazer • люблю Elixir • не люблю RoR і Blockchain • останні півроку моя команда пише великий проект на Hanami...
  3. Hanami, ти хто такий? Давай, до допобачення! • все що

    ви хочете бачити від веб-фреймворка • роутінг • контроллери • релоад коду • робота з БД • міграції • генерація коду • консоль / дебаг • компіляція ассетів
  4. Воу, навіщо, якщо є Рельси?! а ще в “Рельсах” є:

    • гострі ножі / “sharp knives” © DHH • товсті контролери • товсті моделі • товсті вьюшки • strong parameters • бізнес логіка тонким шаром по всьому цоьму • рельс-деформація: вписати все у сутність АР, якщо не лізе - це проблеми бізнес логіки • вихід за рамки Rails-way прирівнюється до самогубства проекту
  5. Що пропонує Hanami? • контейнерну архітектуру (моноліт, що легко розділити)

    • структуру для специфічного і загальновживаного коду • окремий клас на кожен екшн (гнучка настройка на кожен) • інтерактори aka service objects • Entity - Repository замість ActiveRecord • middleware орієнтованість, навіть екшн - middleware • dry-gems • свобода від ActiveSupport
  6. Rails: ActiveRecord class Task < ApplicationRecord scope :high_priority, ->(limit =

    3) { where('priority <= 1').limit(limit) } end Task.create {title: 'homework', priority: 1} Task.high_priority
  7. Hanami: Entity - Repository class Task < Hanami::Entity end class

    TaskRepository < Hanami::Repository def high_priority(limit: 3) tasks.where('priority <= 1').limit(limit) end end task = Task.new {title: 'homework', priority: 1} # task = {title: 'homework', priority: 1} TaskRepository.new.create(task) TaskRepository.new.high_priority
  8. Увага! "Батарейки до комплекту не входять!" • немає “Hanami” адаптованих

    ліб, але завжди працюють просто “rack middleware” орієнтовані • автентифікація - немає Device-подібного рішення, потребує кастомне • кеш - кастомна реалізація з Redis, readthis + hiredis gems • Sidekiq - працює, але треба трошки загуглити • Sidekiq::Web - працює тільки як окремий процесс (Docker рятує) • RSpec/Minitest є з короби, а от Capybara (Hanami <=1.2) / DBCleaner / WebDriver / WebMock доведеться дороблювати методом проб та помилок
  9. Увага! "Батарейки до комплекту не входять!" • factory_bot - тягне

    ActiveSupport, на щастя є Fabricator • pagination - через плагін ROM.rb • soft delete - через monkey patching Hanami::Repository • enum у моделях - через ruby-enum • WebPack(er) - через WebPack и свій хелпер • бізнес логіка - через ваш улюблений сервіс об’єкт • Trailblazer йде окремо, але працює без “танців з бубном” • структура файлів і папок - майже яку забажаєте!
  10. Один класс на один екшн - сумнівно ... context 'with

    valid params' do it 'redirects the user to the books listing' do response = action.call(params) expect(response[0]).to eq(302) expect(response[1]['Location']).to eq('/books') end end context 'with invalid params' do it 're-renders the books#new view' do response = action.call(params) expect(response[0]).to eq(422) end ...
  11. Interactor-и не тягнуть на гарний service object require 'hanami/interactor' class

    AddBook include Hanami::Interactor expose :book def initialize(repository: BookRepository.new) @repository = repository end def call(book_attributes) @book = @repository.create(book_attributes) end end
  12. Trailblazer + dry-validation без макросів Багато коду без готовых макросів,

    а документації на макроси немає :( def validate(ctx, params:, **) sch = Dry::Validation.Form(PostSchema) do required(:type).filled(included_in?: ::Post::Types.values) optional(:body).value(:length_permitted?) optional(:tags).maybe end ctx[:validation_result] = sch.with(type: params[:embed_type]).call(params) ctx[:params] = ctx[:validation_result].to_h handle_validation_errors(ctx) end
  13. Немає кастомних CLI команд Напевно, можна робити rake таски, але

    ми використали gem ‘hanami-cli’ Мінус - різні типи викликів: $ hanami db create $ ./hanami-cli db seed
  14. Hanami::Repository < ROM.rb • загальне враження - не розумієш як

    воно працює • постійно перебираєш варіанти • не підтримується більше одного БД коннекта • вкладені Entity не працюють, все через Hash • не працює кастомний мапінг, завжди “мапить” до однойменного з репозиторієм Entity • як результат - у нашому плані “перейти на Sequel + dry-struct mapping”
  15. Hanami::Repository < ROM.rb по різному працює PostRepo.update(1, embed_data: { ...

    }) та class PostRepo < Repo def some_update() posts.where(uuid: 1).update(embed_data: { ... }) end end
  16. Hanami::Repository < ROM.rb Raw SQL через одну “дивну” функцію class

    UserRepository < Hanami::Repository ... def find_special(params) users.read("SELECT * FROM users INNER JOIN...
  17. Hanami::Repository < ROM.rb def suggest_users_by_query(query, user, count=5) sql = <<-SQL

    (firstname ILIKE :query OR lastname ILIKE :query OR nickname ILIKE :query) AND id != :requester_id SQL users.where( Sequel.lit(sql, query: "#{query}%", requester_id: user.id) ).limit(count).as(User) end
  18. Трохи екзотики jRuby + Celluloid + Hanami > GEMFILE ...

    gem 'celluloid' gem 'reel-rack' gem 'hanami-router' gem 'hanami-controller' gem 'hanami-validations' gem 'dry-system' gem 'sequel'
  19. router.rb Hanami::Controller.configure do handle_exceptions false end WEB_ROUTER = Hanami::Router.new do

    namespace 'api' do namespace 'meetup' do post '/start', to: ApiActions::Meetup::Start end end end
  20. action module ApiActions::Meetup class Start include Hanami::Action params do required(:user_id).filled(:str?)

    end def call(params) render_errors(params) unless params.valid? self.body = “render smth.” end end end
  21. Моє бачення ідеального фреймворка • жоден поважний Рубі-захід не обійдеться

    без реклами Elixir та Phoenix! • складається з незалежних бібліотек (вітання зі світу PHP!) • повна прозорість ходу запиту від проксі, до роутінгу і екшна • middleware на всіх рівнях, middleware pipelines на роутинг • гнучкий data mapper • базовий DDD структуру тек / файлів / модулів
  22. Підсумок • з Hanami можна написати як великий, так і

    маленький застосунок • доведеться “покопати” • дуже мало готових рішень, але вони з’являються • ви знову згадаєте як це - “програмувати” :) • все можна підлаштувати під себе, а не гнутися під фреймворк • добрий вибір для: ◦ довгограючих проектів ◦ API ◦ мікро-сервісних проектів ◦ застосунків без “людського” інтерфейсу