A year (+½) with Hanami in production: the Good, the Bad and some Recipes

A year (+½) with Hanami in production: the Good, the Bad and some Recipes

RubyC 2019 (Kyiv) and GrillRB 2019 (Wroclaw) slides


Yevhen "Eugene" Kuzminov

September 15, 2019


  1. A year (+½) with Hanami in production: the Good, the

    Bad and some Recipes Yevhen [Eugene] Kuzminov Ruby Team Lead in MobiDev
  2. Photo by TOMOKO UJI on Unsplash

  3. whoami • 10 years in professional web dev (put HTML

    together since 2001) • 6 years with PHP + 4 years with Ruby • among them 3 years with Trailblazer • You may know me by ◦ “Ruby Web Dev: The Other Way” book http://rwdtow.stdout.in ◦ http://stdout.in/en/post/ruby-ecosystem-bittersweet-or-we-like-to-hate-php • I am in love with Elixir • “In complicated relationships” with RoR and Blockchain • but last 1+½ years I am a Solution Architect of one cool web app...
  4. Do you know what is Hanami?

  5. Hanami is... • a full featured web framework • routing

    • controllers / actions • dev code reload • DB access • migrations • scaffolding • console / debug • assets handling
  6. What’s wrong with Rails, man? • “sharp knives” © DHH

    • fat controllers • fat models • fat views • fat strong parameters • the whole app is your business logic • Rails-deformation: try to fit any Business Entity in ActiveRecord model if can’t fit - it’s a wrong… Business Entity • going against the Rails-way hurts so much
  7. Hanami proposes: • container architecture (monolith that is easy to

    split) • structure for common and specific code • separate class per action, flexible setting per each action • Interactors aka Service Objects • Entity - Repository instead of ActiveRecord • middleware oriented, even actions are middlewares • dry-gems • being free from ActiveSupport “pollution”
  8. Is Hanami really good?

  9. Yes!

  10. Do I enjoy using Hanami?

  11. Yes!

  12. Would I recommend everyone to start using Hanami right now?

  13. ¯\_(ツ)_/¯

  14. The price of non-mainstream solution • about 3 months to

    become productive, when we started “from scratch” • need to educate each new team member (but they enjoy it :) ) • no known ways to integrate most of existing Ruby code/gems • Rails-ish ecosystems is a bit depressing without Rails • lack of docs, examples, learning resources...
  15. Hanami Cookbook https://hanami-cookbook.stdout.in

  16. The Story

  17. The Story (of one big Hanami app) context of architectural

    and technical decisions SPA, REST, API, SEO, admin area, webhooks and realtime websockets listeners ~ 140 API actions ~ 350 Trailblazer operations ~ 50 entities/models + ~ 50 repositories ~ 30 sidekiq workers 6 different persistent data storages (databases)
  18. Trailblazer (business logic) • Using only gem 'trailblazer-operation' • Macroses

    are ActiveModel oriented, need to write own for Hanami • Validation via pure dry-validation • Hanami’s Interactor is too simplistic • TRB is not as useful as with Rails • Still, perhaps, most powerful Service Objects solution • But docs sometimes not clear and functionality tend to be overcomplicated
  19. REST API • Separate “container app” • Own request handling

    settings, turned off templates + layouts • Trailblazer Representable + Roar • Action calls TRB Operation, then render JSON from Representer • JWT authentication (custom) • Not really straightforward JSON rendering def render_json(data, code) halt(code, Oj.dump(data)) end
  20. Authentication • No out-of-the-box solution • For the REST API

    started with naive JWT token auth • Now switching to gem 'jwt_sessions' by @tuwukee • JWTSessions gives secure and solid foundation • For Cookie-based (non API) auth solution is still to come
  21. WebPack[er] (SPA frontend integration) All you need: • Manifest file

    parser • View helper for `javascript_pack_tag` • Config switcher for WebPack Dev Server usage What you get: • Happy frontend devs • Completely custom WebPack setup • No coupling with WebPack, because it is not needed!
  22. Sidekiq gem 'sidekiq' custom addons (like scheduler and statistics) are

    working too sidekiq -e development -r ./config/boot.rb -C ./config/sidekiq.yml use standard Sidekiq::Worker mixin classes
  23. Here be Dragons!

  24. Hanami::Repository < ROM.RB • general impression - “I don’t understand

    how it works” • constantly trying different options and methods “while it works” • supports only one DB connection • nested Entities not working, return Hashes • custom mapping not working, always maps result to Entity “corresponding” to current Repo • so we did an unexpected move...
  25. Use Sequel::Model, Luke! • Yes, I know it’s ActiveRecord pattern,

    don’t panic! :) • ActiveRecord is not bad when used solely for Data Manipulation • Write simplistic own Repository class • Use Sequel queries ONLY inside the Repository • Flexible usage of Sequel::Model or Dataset • Easy and clear huge complicated raw SQL queries • Don’t forget to before_fork { DB.disconnect }
  26. Many DBs, but one Repo concept We use simultaneously: •

    PostgreSQL • Redis (persistent) • InfuxDB • Cassandra • JanusGraph • Algolia search To be added soon: • Clickhouse • Algolia >>> Elastic Make a Repository class (not inherited from Hanami::Repository) and use any DB client / SDK calls, return structured data
  27. Dry Container + AutoInject • register all deps that require

    single instance • register most Repositories • Dry-Systems could work, but way too complicated # ./config/dry-auto-inject.rb class Container extend Dry::Container::Mixin register('influx', memoize: true) do db_params = {...} InfluxDB::Client.new(ENV['INFLUXDB_DB'], db_params) end … Container['influx'] # include ::Inject['influx']
  28. Cache • no standard cache implementation • Readthis::Cache ideal ActiveSupport

    compatible match • Basic cache methods + custom Redis commands support class PostCacheRepository < CacheRepository def find(uuid) cache_get(cache_id(uuid), expires_in: 3600) do PostRepository.new.find(uuid) end end ...
  29. Running in Production (with Puma) • gem 'puma' # to

    Gemfile group :production • HANAMI_ENV=production • SERVE_STATIC_ASSETS=false • hanami server --no-code-reloading # detects Puma • ./config/puma.rb # almost like for Rails, but with... on_worker_boot do Hanami.boot end
  30. CLI (custom commands) Perhaps you can use plain rake tasks,

    but we used gem ‘hanami-cli’ As a minus - different invocation form: $ hanami db create $ ./hanami-cli db seed
  31. Testing • we use Minitest (long story of my personal

    love and hate) • but would migrate to RSpec sometime (as it is by default in 1.3+) • all your favorite things would work: ◦ Capybara ◦ Database Cleaner ◦ VCR ◦ Webmock ◦ etc. • only Factory Bot requires “ActiveSupport” and replaced by Fabricator • test mostly TRB Operations • do not use “Hanami’s advertised” Action tests via “.call”
  32. ActionPolicy (Authorization) • any Ruby Auth/Policy lib can be used

    • the less ActionController code/helpers, the better • gem 'action_policy' by Evil Martians common usage with Trailblazer Operation: step :check_policy, Output(:failure) => End(:forbidden) def check_policy(ctx, **) ::PostPolicy.new(user: ctx[:user], post: ctx[:model]).apply(:create?) end
  33. WebSockets - A(ction)nyCable gem 'anycable' gem 'litecable' • did I

    mention that Evil Martians ? • requires additional configuration and separate process • LiteCable gem integrates well with Hanami • really fluent and fast operation, very low RAM consumption • clearer Channels code and messages broadcasting logic (that ActionCable) • uses ActionCable JS on frontend
  34. Services communication (RabbitMQ) gem 'sneakers' • Consider this very similar

    to Sidekiq, but not limited to Ruby clients • sneakers work --require config/sneakers_boot.rb class MakeAKeynote include Sneakers::Worker from_queue 'rubyc.keynotes' def work(msg) # do smth with `msg` ack! end end
  35. Container apps, Services and Monorepo

  36. Hanami as a modular puzzle # GEMFILE #jRuby v9.2.4 ...

    gem 'celluloid' # was this not a perfect idea? well… yes. gem 'reel-rack' gem 'hanami-router' gem 'hanami-controller' gem 'hanami-validations' gem 'dry-system' gem 'sequel'
  37. #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
  38. #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
  39. #action class module ApiActions::RubyC class Start include Hanami::Action params do

    required(:speechs_id).filled(:str?) end def call(params) render_errors(params) unless params.valid? self.body = “Ruby, Ruby, RubyC!” end end end
  40. Hanami 2 • In progress, but no exactly clear timeline

    • https://discourse.hanamirb.org/t/roadmap-for-v2-0-0-alpha1/475 • https://www.youtube.com/watch?v=LqGBhTSOmTI • Less internal state • More functional Containers • More flexible Configuration • Fresh Dry Gems
  41. My vision of ideal backend-web framework • any Ruby conf

    can not take place without mentioning Elixir/Phoenix • composed from loosely coupled libs (greeting from PHP!) • full request path transparency from app server, through router to action • middlewares on all layers, router middleware pipelines • flexible data mapper (maybe even not an ORM) • basic DDD-ish structure for files/folders/modules • not being Resources/CRUD oriented • framework should teach me to write a better apps, to do better architectures
  42. None
  43. None
  44. Ruby / Hanami Elixir / Phoenix

  45. “model” “model” “service” “context” “context” “service” “model” “model”

  46. Facing reality • It’s impossible to “beat Rails” in CRUD

    apps • No need even to try • Clear “complex domain” app niche positioning needed for Hanami • Not trying to pretend “you can do the same even better and faster” than Rails
  47. Summary • with Hanami it’s possible to do both small

    and big apps • “batteries not included” • a few compatible out-of-the-box Ruby libs, but they are growing! • you would need to write some code :) • it is you are adjusting the framework, not framework adjusts you • good choice for: ◦ non-CRUD long running projects with complex domain (it is our case) ◦ API ◦ (micro) service oriented projects ◦ non WebUI apps
  48. Hanami is not perfect, but it deserves your attention and

  49. Questions? Yevhen [Eugene] Kuzminov Ruby Team Lead in MobiDev http://stdout.in