Background: Hanami in Enterprise Systems
Hanami (formerly Lotus) was designed to overcome the monolithic tendencies of Rails by embracing modularity. Each Hanami app is a collection of micro-apps, with repositories abstracting data access and actions representing endpoints. While this design improves maintainability, it introduces complexity when scaling across large teams and environments.
Why Hanami Matters
In enterprise contexts, Hanami's strict boundaries and immutability reduce accidental side effects. However, these benefits come with a cost: debugging failures requires tracing through explicit containers, contracts, and repositories rather than relying on Rails-style magic.
Architectural Implications
Hanami's architecture centers on the container and repository patterns:
- Repositories abstract persistence but can mask inefficient queries if not carefully optimized.
- Entities are immutable by default, requiring explicit replacements rather than in-place changes.
- Containers wire dependencies, and misconfigurations here cause runtime errors that are difficult to trace.
Case: Database Scaling
Repositories map directly to SQL queries. When datasets scale into millions of rows, unoptimized repository methods can cause performance bottlenecks. Unlike Rails, Hanami does not silently lazy-load associations, so misdesigned queries surface immediately in production workloads.
Diagnostics and Root Cause Analysis
Senior engineers troubleshoot Hanami systems using a combination of framework-level and OS-level diagnostics:
- Inspect SQL queries generated by repositories with
HANAMI_ENV=development
logging. - Use
stackprof
orruby-prof
to identify bottlenecks in actions or services. - Enable
GC::Profiler
to detect memory leaks in long-lived processes. - Check container wiring with
Hanami.app.container.keys
to verify dependency registrations.
Detecting Repository Inefficiency
class OrderRepository < Hanami::Repository def expensive_orders(limit: 100) orders.where { total > 1000 }.limit(limit).to_a end end
If limit
is not set properly, this method can load millions of rows, causing memory pressure.
Pitfalls in Troubleshooting
Common mistakes include:
- Assuming repository queries behave like ActiveRecord and lazy-load. In Hanami, queries must be explicit.
- Forgetting entity immutability, leading to bugs when developers expect in-place updates.
- Misconfiguring container dependencies, causing runtime
Dry::Container::Error
exceptions. - Mixing Rails-style service layers with Hanami's clean architecture, creating hybrid patterns that are harder to debug.
Step-by-Step Fixes
1. Resolving Repository Performance Issues
Optimize queries by adding database indexes and using explicit limits.
class UserRepository < Hanami::Repository def recent_signups(days: 30) users.where { created_at > Date.today - days }.order(:created_at).limit(500).to_a end end
2. Fixing Dependency Injection Errors
Check container registrations when a service cannot resolve dependencies.
Hanami.app.container.keys # Ensure services are registered Hanami.app.container.register(:mailer, Mailer.new)
3. Handling Memory Leaks in Workers
Long-lived Hanami apps on Puma or Falcon may accumulate memory due to improper object retention. Profile with GC and restart workers gracefully.
GC::Profiler.enable at_exit { puts GC::Profiler.result }
4. Managing Concurrency Conflicts
When running with threaded servers, shared mutable state can leak across requests. Enforce immutability in services and prefer thread-safe containers.
5. Debugging Entity Behavior
Entities are immutable. Developers must create replacements instead of mutating fields.
order = OrderRepository.new.find(1) updated = order.with(status: 'shipped') OrderRepository.new.update(order.id, updated.to_h)
Best Practices for Long-Term Stability
- Adopt SQL monitoring to detect expensive queries early.
- Document all container registrations to avoid runtime surprises.
- Keep business logic in services and repositories, not controllers.
- Enforce immutability to prevent subtle concurrency bugs.
- Regularly profile memory and CPU usage in production workloads.
Conclusion
Hanami provides a clean and modular architecture, but troubleshooting requires deeper architectural insight compared to Rails. Engineers must adapt to explicit repository queries, immutable entities, and container-based dependency injection. By proactively monitoring performance, validating container configurations, and enforcing immutability, enterprises can deploy Hanami with confidence in high-scale environments.
FAQs
1. Why are my Hanami queries slow compared to Rails?
Hanami does not lazy-load associations. Queries must be explicitly optimized with indexes, limits, and ordering to avoid large in-memory loads.
2. How do I fix dependency resolution errors in Hanami?
Check the container registration keys. If a dependency is missing, register it explicitly using Hanami.app.container.register
.
3. Can Hanami handle multi-threaded servers like Puma?
Yes, but services must be thread-safe. Avoid mutable shared state and enforce immutability in entities and services.
4. What's the best way to handle memory leaks in Hanami apps?
Profile with GC::Profiler
or stackprof
, release unused objects, and restart long-lived workers periodically. Ensure repositories don't load excessive rows.
5. How should enterprises migrate from Rails to Hanami?
Start by moving isolated services or APIs to Hanami while keeping legacy systems in Rails. Gradual adoption allows teams to learn repository and container patterns before full migration.