Slide 1

Slide 1 text

A year (+½) with Hanami in production: the Good, the Bad and some Recipes Yevhen [Eugene] Kuzminov Ruby Team Lead in MobiDev

Slide 2

Slide 2 text

Photo by TOMOKO UJI on Unsplash

Slide 3

Slide 3 text

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...

Slide 4

Slide 4 text

Do you know what is Hanami?

Slide 5

Slide 5 text

Hanami is... ● a full featured web framework ● routing ● controllers / actions ● dev code reload ● DB access ● migrations ● scaffolding ● console / debug ● assets handling

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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”

Slide 8

Slide 8 text

Is Hanami really good?

Slide 9

Slide 9 text

Yes!

Slide 10

Slide 10 text

Do I enjoy using Hanami?

Slide 11

Slide 11 text

Yes!

Slide 12

Slide 12 text

Would I recommend everyone to start using Hanami right now?

Slide 13

Slide 13 text

¯\_(ツ)_/¯

Slide 14

Slide 14 text

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...

Slide 15

Slide 15 text

Hanami Cookbook https://hanami-cookbook.stdout.in

Slide 16

Slide 16 text

The Story

Slide 17

Slide 17 text

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)

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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!

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

Here be Dragons!

Slide 24

Slide 24 text

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...

Slide 25

Slide 25 text

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 }

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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']

Slide 28

Slide 28 text

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 ...

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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”

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

Container apps, Services and Monorepo

Slide 36

Slide 36 text

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'

Slide 37

Slide 37 text

#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

Slide 38

Slide 38 text

#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

Slide 39

Slide 39 text

#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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

No content

Slide 43

Slide 43 text

No content

Slide 44

Slide 44 text

Ruby / Hanami Elixir / Phoenix

Slide 45

Slide 45 text

“model” “model” “service” “context” “context” “service” “model” “model”

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

Hanami is not perfect, but it deserves your attention and love!

Slide 49

Slide 49 text

Questions? Yevhen [Eugene] Kuzminov Ruby Team Lead in MobiDev http://stdout.in https://twitter.com/iJackUA