Skip to content

Architecture Overview ​

CleanSlice combines two architectural patterns: Clean Architecture for layer separation and Vertical Slices for feature organization. Together, they create a codebase that scales without becoming tangled.

Clean Architecture ​

Clean Architecture, introduced by Robert C. Martin, organizes code into concentric layers where dependencies always point inward — from outer infrastructure toward inner business logic.

CleanSlice adapts this into three practical layers:

┌─────────────────────────────────────────────┐
│  Presentation                               │
│  (Controllers, DTOs, Pages, Components)     │
└──────────────────┬──────────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────┐
│  Domain                                     │
│  (Services, Types, Gateway Interfaces)      │
└──────────────────┬──────────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────┐
│  Data                                       │
│  (Gateway Implementations, Mappers)         │
└─────────────────────────────────────────────┘

The key principle: the domain layer has no external dependencies. It doesn't know about HTTP, databases, or UI frameworks. This makes business logic portable, testable, and resilient to infrastructure changes.

Vertical Slices ​

Instead of grouping all controllers together, all services together, and all repositories together, CleanSlice groups code by feature. Each feature (user management, projects, chat, etc.) is a self-contained "slice" with its own layers.

This means:

  • Adding a feature = adding a folder
  • Removing a feature = removing a folder
  • Understanding a feature = reading one folder
  • Features don't interfere with each other

Combining Both Patterns ​

Each slice contains its own three-layer architecture:

slices/
├── user/                          # User feature
│   ├── user.controller.ts         #   Presentation
│   ├── domain/                    #   Domain
│   │   ├── user.types.ts
│   │   ├── user.gateway.ts
│   │   └── user.service.ts
│   └── data/                      #   Data
│       ├── user.gateway.ts
│       └── user.mapper.ts
│
├── project/                       # Project feature
│   ├── project.controller.ts      #   Presentation
│   ├── domain/                    #   Domain
│   └── data/                      #   Data
│
└── prisma/                        # Setup slice (infrastructure)
    ├── prisma.module.ts
    └── prisma.service.ts

Design Principles ​

1. Dependencies Point Inward ​

The domain layer never imports from the data layer. Instead, it defines abstract interfaces (gateways) that the data layer implements. Dependency injection connects them at runtime.

2. Framework Isolation ​

Domain types and business logic are plain TypeScript. They work independently of NestJS, Prisma, Vue, or any other framework. This means you can test them without spinning up a server or a database.

3. Explicit Boundaries ​

Each layer has a clear responsibility:

  • Presentation handles input/output formatting
  • Domain contains business rules
  • Data handles external communication

Code in one layer doesn't reach into another layer's internals.

4. Slice Independence ​

Slices interact through their public domain interfaces, not through internal implementation details. If the project slice needs user data, it imports from #user/domain, not from #user/data.

What's Next? ​

  • Layers — Detailed breakdown of each layer's responsibilities
  • Slices — How to structure and compose slices
  • Subslices — Grouping related slices under a parent namespace
  • Dependency Flow — Import rules and dependency injection

Built with CleanSlice