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

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

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

Avatar for Yevhen "Eugene" Kuzminov

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 ◦ мікро-сервісних проектів ◦ застосунків без “людського” інтерфейсу