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

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

Ac3e162318c73347bef4d20b1bb7f7f3?s=128

Yevhen "Eugene" Kuzminov

November 01, 2018
Tweet

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. Базова структура Hanami застосунка, контейнери

  7. 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
  8. 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
  9. Увага! "Батарейки до комплекту не входять!" • немає “Hanami” адаптованих

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

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

  12. None
  13. Гарна сторона Hanami Не така вже й гарна сторона Hanami

  14. Один класс на один екшн - сумнівно ... 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 ...
  15. 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
  16. 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
  17. Немає кастомних CLI команд Напевно, можна робити rake таски, але

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

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

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

    UserRepository < Hanami::Repository ... def find_special(params) users.read("SELECT * FROM users INNER JOIN...
  22. 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
  23. Трохи екзотики jRuby + Celluloid + Hanami > GEMFILE ...

    gem 'celluloid' gem 'reel-rack' gem 'hanami-router' gem 'hanami-controller' gem 'hanami-validations' gem 'dry-system' gem 'sequel'
  24. config.ru require 'rubygems' require 'bundler/setup' require 'celluloid' Bundler.require(:default) require_relative './system/boot.rb'

    require_relative './router.rb' Supervisors::Main.run! run WEB_ROUTER
  25. 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
  26. 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
  27. Моє бачення ідеального фреймворка • жоден поважний Рубі-захід не обійдеться

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

  29. “model” “model” “service” “context” “context” “service” “model” “model”

  30. Підсумок • з Hanami можна написати як великий, так і

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

    йому шанс!
  32. ДЯКУЮ ЗА УВАГУ! ПИТАННЯ? Євген Кузьмінов Ruby Team Lead http://stdout.in

    https://twitter.com/iJackUA