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