[![hackmd-github-sync-badge](https://hackmd.io/cJug-bPMTluhOqRcKi_Rlg/badge)](https://hackmd.io/cJug-bPMTluhOqRcKi_Rlg) ## 1. Philosophy ### 1-1. The Rails Way (Traditional MVC) - **Convention over Configuration** - opinionated defaults, rapid prototyping - **3 layers**: Models (ActiveRecord), Views (templates), Controllers (orchestration) - **Strength**: Short path from idea to implementation, perfect for MVPs - **Challenge**: As apps grow → Fat Models + Fat Controllers + callback hell ### 1-2. The Extended Rails Way (Layered) - **Structured abstractions** when complexity emerges - **5+ specialized layers**: Infrastructure, Data/Persistence, Application/Service, Business/Domain, Presentation - **Strength**: Clear boundaries, testable, maintainable at scale - **Challenge**: More files, upfront design, steeper learning curve ## 2. Layer Architecture ### 2-1. Traditional MVC Rails ``` ╔═══════════════════════════════════════════════════════════════════╗ ║ 👤 FRONTEND (Browser/Mobile/SPA) ║ ╠═══════════════════════════════════════════════════════════════════╣ ║ • HTML page rendering ║ ║ • JavaScript (jQuery/vanilla) ║ ║ • AJAX calls ║ ╚═══════════════════════════════════════════════════════════════════╝ ▲ │ Response │ │ Request (HTML/JSON mixed) │ │ (GET/POST/PUT/DELETE) │ ▼ ╔═══════════════════════════════════════════════════════════════════╗ ║ 🖥️ BACKEND (Rails Monolithic Server) ║ ╠═══════════════════════════════════════════════════════════════════╣ ║ ║ ║ ┌─────────────────────────────────────────────────────────────┐ ║ ║ │ 🌐 ROUTING │ ║ ║ │ config/routes.rb │ ║ ║ └─────────────────────────────────────────────────────────────┘ ║ ║ ↓ ║ ║ ┌─────────────────────────────────────────────────────────────┐ ║ ║ │ 📥 CONTROLLERS (Fat) │ ║ ║ │ • HTTP handling + Business logic │ ║ ║ │ • Authorization + Validation │ ║ ║ │ • Direct model access │ ║ ║ └─────────────────────────────────────────────────────────────┘ ║ ║ ↓ ↓ ↓ ║ ║ ┌────────────────┐ ┌──────────────────┐ ┌─────────────────┐ ║ ║ │ 📄 VIEWS │ │ 💾 MODELS (Fat) │ │ 🔧 OTHERS │ ║ ║ │ │ │ │ │ │ ║ ║ │ • ERB/HAML │ │ • Persistence │ │ • Jobs │ ║ ║ │ • Helpers │◄─│ • Validations │ │ • Mailers │ ║ ║ │ • Partials │ │ • Callbacks │ │ • Config │ ║ ║ │ • Jbuilder │◄─│ • Business logic │ │ • Lib │ ║ ║ │ • @vars │ │ • Associations │ │ │ ║ ║ │ │ │ • Scopes │ │ │ ║ ║ │ │ │ • Presentation │ │ │ ║ ║ │ │ │ • Serialization │ │ │ ║ ║ └────────────────┘ └──────────────────┘ └─────────────────┘ ║ ║ ↓ ║ ║ 💿 Database ║ ║ ║ ╚═══════════════════════════════════════════════════════════════════╝ ⚠️ Issues: • No clear separation between frontend and backend concerns • Frontend tightly coupled to backend structure • API responses expose database schema directly • HTML and JSON responses mixed in same controllers/views • Hard to evolve frontend independently ``` ### 2-2. Layered Rails ``` ╔═══════════════════════════════════════════════════════════════════╗ ║ 👤 FRONTEND (React/Vue/Angular/Mobile) ║ ╠═══════════════════════════════════════════════════════════════════╣ ║ • Component-based UI (React/Vue) ║ ║ • State management (Redux/Vuex/MobX) ║ ║ • Type-safe interfaces (TypeScript) ║ ║ • Independent deployment ║ ╚═══════════════════════════════════════════════════════════════════╝ ▲ │ JSON │ │ HTTP/REST/GraphQL Status: 200/201/4xx │ │ Authorization: Bearer token Headers: ETag, Cache │ │ Content-Type: application/json │ ▼ ╔════════════════════════════════════════════════════════════════════╗ ║ 📡 API CONTRACT LAYER ║ ╠════════════════════════════════════════════════════════════════════╣ ║ Versioned, Stable API Responses (Independent of DB Schema) ║ ║ ║ ║ GET /api/v1/users/123 → { "id": 123, "full_name": "...", ║ ║ "email": "...", "avatar_url": "..." } ║ ╚════════════════════════════════════════════════════════════════════╝ ▲ ══════════════════════════════╬═══════════════════════════════════════ ▼ ╔═══════════════════════════════════════════════════════════════════╗ ║ 🖥️ BACKEND (Rails API Server) ║ ╠═══════════════════════════════════════════════════════════════════╣ ║ ║ ║ ┌─────────────────────────────────────────────────────────────┐ ║ ║ │ 🌐 ROUTING (API Versioning) │ ║ ║ │ namespace :api do namespace :v1 do ... end end │ ║ ║ └─────────────────────────────────────────────────────────────┘ ║ ║ ↓ ║ ║ ┌─────────────────────────────────────────────────────────────┐ ║ ║ │ 📥 INTERFACE LAYER (Thin Controllers) │ ║ ║ │ • HTTP handling only │ ║ ║ │ • Middleware: Auth, CORS, Rate Limit │ ║ ║ │ • Delegates to services │ ║ ║ └─────────────────────────────────────────────────────────────┘ ║ ║ ↓ ║ ║ ┌─────────────────────────────────────────────────────────────┐ ║ ║ │ 🎯 APPLICATION LAYER (Orchestration) │ ║ ║ │ • Service Objects • Form Objects │ ║ ║ │ • Command Objects • Filter Objects │ ║ ║ └─────────────────────────────────────────────────────────────┘ ║ ║ ↓ ║ ║ ┌─────────────────────────────────────────────────────────────┐ ║ ║ │ 💼 BUSINESS/DOMAIN LAYER (Pure Logic) │ ║ ║ │ • Domain Models • Value Objects │ ║ ║ │ • Policy Objects • Domain Events │ ║ ║ └─────────────────────────────────────────────────────────────┘ ║ ║ ↓ ║ ║ ┌─────────────────────────────────────────────────────────────┐ ║ ║ │ 💾 DATA/PERSISTENCE LAYER │ ║ ║ │ • Repositories • Query Objects │ ║ ║ │ • ActiveRecord (persistence only) │ ║ ║ └─────────────────────────────────────────────────────────────┘ ║ ║ ↓ ║ ║ 💿 Database ║ ║ ║ ║ ┌──────────────────────┐ ┌──────────────────────────────────┐ ║ ║ │ 🎨 PRESENTATION │ │ 🔧 INFRASTRUCTURE │ ║ ║ │ (Parallel Layer) │ │ (Parallel Layer) │ ║ ║ │ │ │ │ ║ ║ │ • Serializers (API) │ │ • Background Jobs │ ║ ║ │ • Presenters (Web) │ │ • Logging & Monitoring │ ║ ║ │ • Components (SSR) │ │ • Config & Secrets │ ║ ║ │ • Version-specific │ │ • External Service Adapters │ ║ ║ │ response formats │ │ • Cache Management │ ║ ║ └──────────────────────┘ └──────────────────────────────────┘ ║ ║ ▲ ║ ║ │ ║ ║ └─ Transforms domain objects into ║ ║ versioned API responses (flows back up) ║ ║ ║ ╚═══════════════════════════════════════════════════════════════════╝ ✅ Benefits: • Frontend and backend completely decoupled • API contracts stable and versioned (v1, v2) • Frontend can evolve independently • Backend schema changes don't break frontend • Multiple frontend clients (web/mobile/desktop) use same API • Clear separation of concerns at every layer ``` ## 3. MVC vs Layered: Quick Comparison |Responsibility |MVC |Layered | |:-|:-|:-| |**Authorization** |Scattered in controllers/models |`app/policies/` - single source of truth | |**Business Logic** |Mixed everywhere |Domain models + `app/services/` | |**Data Access** |Direct ActiveRecord everywhere |Repositories + Query Objects | |**Form Handling** |Strong params + model validations |Form Objects (context-sensitive) | |**Presentation** |`@instance_vars` + helpers + `#as_json` |Presenters + Serializers + View Components | |**Use Cases** |Hidden in controller actions |Explicit Service Objects | ## 4. Key Components on The Extended Rails Way (Layered) ### 4-1. Application/Service Layer - **Service Objects**: `User::RegistrationService.call(params)` - single business operation - **Form Objects**: `CheckoutForm.new(params).save` - multi-model operations, context validations - **Filter Objects**: `ProjectsFilter.new(params).apply(scope)` - user-driven queries ### 4-2. Business/Domain Layer - **Domain Models**: Rich behavior, no persistence details - **Policy Objects**: `PostPolicy.new(user, post).publish?` - authorization rules - **Value Objects**: `Money.new(100, 'USD')` - immutable domain concepts ### 4-3. Data/Persistence Layer - **Repositories**: `PostRepository.published` - data access abstraction - **Query Objects**: `ActiveUsersQuery.call` - reusable complex queries - **ActiveRecord**: Stripped down to persistence only ### 4-4. Presentation Layer - **View Components**: `render SearchBox::Component.new(url:, placeholder:)` - explicit interfaces - **Presenters**: `UserPresenter.new(user).display_name` - context-aware formatting - **Serializers**: `UserSerializer.new(user).as_json` - API contracts, versioning ## 5. When to Use Layered Approach? ### 5-1. ✅ Good Fit - Beyond MVP stage - Teams of 3+ developers - Complex business domains - Long-term maintenance - Multiple API clients - Performance requirements ### 5-2. ❌ Overkill - Simple CRUD apps - Prototypes/MVPs - 1-2 person teams - Short-lived projects ## 6. Pros & Cons ### 6-1. Pros (Benefits of Layered Approach) - **Maintainability**: Clear separation, easy to locate code - **Testability**: Isolated layers, less framework coupling - **Scalability**: Linear growth, independent layers - **Flexibility**: Swap implementations, context-sensitive behavior - **Clarity**: Code speaks business language ### 6-2. Cons (Challenges/Tradeoffs) - **Initial Complexity**: More files, steeper learning curve - **Slower Start**: No scaffolding support, overhead for simple features - **Team Alignment**: Everyone must follow patterns consistently - **Debugging**: Longer call stacks, more indirection - **Migration Cost**: Refactoring effort for existing apps ## 7. Pragmatic Adoption Strategy 1. **Start with Rails Way** - traditional MVC for new projects 2. **Watch for pain points**: - Models > 200 lines - Controllers with complex workflows - Slow, brittle tests 3. **Extract gradually** - refactor specific areas as needed 4. **Follow conventions** - maintain Rails idioms in new layers 5. **Keep hybrid** - layering where complex, traditional where simple ## 8. Key Takeaway **Traditional MVC**: Rapid initial development, struggles with scale **Layered Approach**: Upfront cost, long-term maintainability The best approach is **pragmatic evolution** - start simple, add structure when complexity demands it. Not dogma, but disciplined refactoring guided by real pain points.