We have all been there - You’re debugging some “critical” bug at 2 AM, with your manager breathing down your neck. Do you:
- A) Hunt through 4 different folders (controllers, services, repositories, models)
- B) Open one folder and find everything user-related in one place
If you picked A, you’re probably organizing your code by technical layers. If B sounds better, you’re ready to learn about Feature Slice architecture.
After 40-odd years of watching codebases turn into maintenance nightmares, I’ve learned that how you organize your code determines whether you ship features fast or spend your weekends debugging.
Feature Slice Organisation
Organise code by features rather than technical layers to reduce cognitive load and improve maintainability.
Traditional Layer Problem
controllers/ services/ repositories/ models/
- Requires bouncing between multiple folders for single feature changes
- Creates artificial separation of related concepts
Feature Slice Solution
users/
├── User.java (Entity)
├── UserRepository.java (Data access)
├── UserService.java (Business logic)
├── UserController.java (Web layer)
├── CreateUserRequest.java (DTOs)
└── UserNotFoundException.java (Exceptions)
Benefits:
- Everything related lives in one place
- Easier debugging and maintenance
- Natural microservice boundaries
- Reduced coupling between features
Use Case Design: Service Classes vs One-Class-Per-Use-Case
Service Class Approach (Traditional)
Single service class containing multiple related operations:
@Service
public class UserService {
public User createUser(CreateUserRequest request) { ... }
public User updateUser(Long userId, UpdateUserRequest request) { ... }
public void deleteUser(Long userId) { ... }
}
One-Class-Per-Use-Case Approach
Separate class for each business operation:
@Component
public class CreateUserUseCase {
public User execute(CreateUserRequest request) { ... }
}
Real World Test
Apply these questions to determine architecture choice:
Use One-Class-Per-Use-Case when:
- Method > 20 lines of code
- Complex business rules involved
- Touches multiple domains/services
- Requires isolated testing of complex scenarios
- Cross-cutting concerns (reporting, complex workflows)
Keep in Service Class when:
- Simple CRUD operations
- Straightforward business logic
- Single-domain operations
- Operations naturally grouped together
Pragmatic Hybrid Approach
@Service
public class UserService {
// Simple operations stay here
public User createUser(CreateUserRequest request) { ... }
public User getUserById(Long id) { ... }
// Complex operations delegate to use cases
public User processAccountVerification(Long userId, VerificationRequest request) {
return accountVerificationUseCase.execute(userId, request);
}
}
Key Principles
- Solve real problems, not theoretical ones
- Start simple, extract complexity when it emerges
- Prioritise readability and maintainability over architectural purity
- Feature cohesion > technical layer separation
Supporting Material
- Rico Fritzsche. “Objects are dead, Long Live Feature Slices”
- César Ferreira. “Package by Feature Not Layers”
- M. Enes Oral. “Package by Layer vs Package by Feature”
- Fowler, M. (2015).“Microservices: A definition of this new architectural term.”
- Palermo, J. (2008). “The Onion Architecture.”
- Shaw, S. (2018). “Vertical Slice Architecture.”
- Martin, R. C. (2012). “The Clean Architecture:”
- Steve Bishop. “Clean Architecture is NOT a project structure”