Understanding Hanami's Design Philosophy

Modularity and Architecture

Hanami promotes a domain-driven design approach where apps are separated by context (e.g., Admin, API, Web). Each slice has its own models, views, and controllers. Unlike Rails' monolithic structure, this separation helps enforce boundaries but complicates shared resource usage.

Dependency Injection and Configuration

Hanami encourages dependency injection and avoids global state. This makes testing straightforward but increases setup overhead and requires explicit bootstrapping logic for external libraries and internal services.

Root Causes of Common Issues

1. Misconfigured Container Registrations

Hanami apps rely on containers (via dry-system) to register components. Incorrect or missing registrations often result in runtime errors that are hard to trace because the system defers loading until access time.

# Example: Incorrect registration
container.register("mailer", MyMailer)

# Correct way with identifier
container.register("notifications.mailer", MyMailer)

2. Concurrent Execution Bottlenecks

Hanami uses Puma or Falcon as its web server, but when multi-threaded code touches shared mutable state (like in-memory caches or singleton services), it may trigger race conditions or inconsistent state.

3. ORM and Repository Confusion

Hanami's separation of persistence logic through repositories can confuse developers accustomed to ActiveRecord. Repository methods must be explicitly defined, and misuse can lead to redundant queries or incorrect state mutations.

4. Inconsistent Booting in Multi-App Setups

In large systems with multiple Hanami apps, inconsistent boot order or misaligned lifecycle hooks may cause parts of the system (like database connections or message queues) to initialize prematurely or not at all.

Diagnostics and Debugging Techniques

Enable Container Logging

Wrap container registrations in logging wrappers to trace what components are being booted and when. This helps track missing dependencies or circular references.

container.register("my_service") do
  puts "Booting my_service"
  MyService.new
end

Check Thread Safety of Custom Services

Use Ruby's Thread API or concurrent-ruby to test service objects for thread safety. Prefer immutable data structures when passing shared state across fibers or threads.

Repository Test Coverage

Repositories should be fully unit tested using mock data or in-memory SQLite to prevent regressions. Coverage often exposes subtle issues with incorrect joins or missing transaction boundaries.

Remediation Strategies

1. Audit and Refactor Container Keys

Standardize naming conventions for container keys (e.g., domain.service.class) and group them logically. This improves maintainability and helps avoid silent overwrites.

2. Use Component Auto-Registration Carefully

Hanami supports auto-registration of components in lib/. Ensure filenames match class paths, or registration will silently fail.

# File: lib/billing/invoice_sender.rb
# Class: Billing::InvoiceSender
# Container key should be: billing.invoice_sender

3. Avoid Global Mutable State

Refactor service objects to be stateless or scoped per-request. Inject dependencies explicitly, even if repetitive—it avoids side effects and concurrency bugs.

4. Align Lifecycle Hooks Across Apps

Ensure that boot hooks like after_initialize or before_shutdown are consistently defined across all sub-apps. Use a shared initializer for common resources such as loggers or DB connections.

Best Practices for Scalable Hanami Apps

  • Use bounded contexts for service isolation.
  • Prefer command and query separation in repository design.
  • Centralize logging and error tracking across all apps.
  • Leverage Hanami's slice feature to enforce modularity.
  • Integrate contracts (e.g., Dry::Validation) for consistent input validation.

Conclusion

Hanami is a powerful and flexible framework tailored for clean, scalable Ruby applications. Yet its very strength—modularity and explicitness—can become complex in enterprise-grade systems. By embracing clear dependency boundaries, thread-safe architecture, and strict repository control, teams can harness Hanami effectively. A proactive approach to diagnostics and lifecycle management ensures robust and fault-tolerant back-end systems.

FAQs

1. Can I integrate Hanami apps with legacy Rails code?

Yes, but it requires bridging infrastructure—usually via JSON APIs or shared services. Avoid mixing ORM layers to reduce coupling.

2. How does Hanami handle multi-threaded workloads?

Hanami can run under thread-safe web servers like Puma, but developers must ensure custom services do not share mutable state between threads.

3. What are the main differences between Hanami and Rails for large systems?

Hanami offers better modularity and test isolation, whereas Rails favors convention and developer speed. Hanami scales better across domain boundaries.

4. How can I manage environment-specific configuration in Hanami?

Use ENV variables and separate config files per environment. Hanami supports dry-configurable, which makes it easy to define dynamic settings.

5. What is the best way to structure repositories in a microservices context?

Keep repositories private to each domain slice. Avoid shared base classes, and focus on exposing use-case-specific methods only.