DEV Community

Luis Donis
Luis Donis

Posted on

The Rails SaaS Architecture I Wish I Had 5 Years Ago

The Rails SaaS Architecture I Wish I Had 5 Years Ago

Modular rails app diagram

Five years ago, I started building SaaS applications with Ruby on Rails.

Like most Rails apps, everything started beautifully simple.

A few models.
A few controllers.
Some concerns.
Some service objects.

It felt clean.

Six months later?

  • Business logic everywhere
  • “Temporary” modules that became permanent
  • Cross-cutting features tangled across the app
  • Shared helpers doing too much
  • Growing fear of refactoring core features

The app still worked — but the architecture wasn’t intentional anymore.

And that’s the real problem.


The Pattern I Kept Repeating

Every SaaS I built ended up having the same core capabilities:

  • Authentication
  • Roles & permissions
  • Notifications
  • Audit logs
  • Dashboards
  • Support tickets
  • Document management
  • Account scoping / multi-tenancy

But I kept rebuilding them inside the main app.

Every. Single. Time.

And every time, they slowly leaked into everything else.


The Architecture Shift

At some point I started asking myself:

What if SaaS capabilities weren’t just folders inside /app?
What if they were isolated systems?

Instead of structuring only by MVC, I started structuring by capability.

Each major SaaS feature became its own Rails engine:

  • Auth engine
  • Support engine
  • Audit engine
  • Dashboard engine
  • Admin engine

Each engine:

  • Owns its models
  • Owns its controllers
  • Owns its routes
  • Owns its views
  • Owns its database tables
  • Has explicit integration points

No leaking constants.\
No random cross-feature helpers.\
No accidental coupling.

Just clear boundaries.


Why Engines?

Rails engines are often misunderstood.

Many developers think they’re only for gems or mountable admin panels.

But for SaaS architecture, they provide something incredibly valuable:

Structural isolation.

When features live in engines:

  • You can reason about them independently
  • You can test them in isolation
  • You can disable or replace them
  • You reduce accidental coupling
  • You avoid the “god app” problem

The main Rails app becomes the orchestrator — not the dumping ground.


What This Changes in Practice

Instead of this:

app/models/user.rb
app/models/role.rb
app/models/ticket.rb
app/models/audit_log.rb
Enter fullscreen mode Exit fullscreen mode

You get something like this:

engines/auth/app/models/auth/user.rb
engines/support/app/models/support/ticket.rb
engines/audit/app/models/audit/log.rb
Enter fullscreen mode Exit fullscreen mode

Clear ownership.

Clear responsibility.

Clear boundaries.


The Real Benefit: Cognitive Load

The biggest improvement wasn’t performance.

It wasn’t scalability.

It was mental clarity.

When a bug appears in support logic, I know exactly where to go.

When I need to extend auditing, I don’t fear breaking unrelated features.

Architecture stops being accidental.

It becomes intentional.


Is This Over-Engineering?

For small apps?

Probably.

For serious SaaS products?

Not really.

Once your application has:

  • Multiple accounts
  • Role systems
  • Permissions
  • Notifications
  • Background processing
  • Operational tooling

You’re no longer building “just a Rails app.”

You’re building infrastructure.


What I’m Doing Now

I’m currently building an open-source Rails framework based on this idea.

Each SaaS capability lives in its own engine.

The goal isn’t to abstract everything.

The goal is to start with intentional structure — instead of cleaning up chaos later.

It’s still evolving.

I’m still learning.

But it already feels more sustainable than anything I built five years ago.

Top comments (2)

Collapse
 
markharbison profile image
Mark Harbison

Great article!

Mental Clarity is the key to all good architecture .

Domain-level restructuring is exactly what I'm focusing on for my products right now. Engines seem like a fantastic approach.

I am interested to see how things go with your framework.

Collapse
 
ldonis profile image
Luis Donis

Appreciate that 🙌

Once features are isolated by capability instead of just folders, the whole system becomes easier to reason about.

I’ll definitely share more as the framework evolves.