Feature Slice Architecture and Use Case Design Patterns

Posted by Riomhaire Research on Thursday, July 24, 2025

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

  1. Solve real problems, not theoretical ones
  2. Start simple, extract complexity when it emerges
  3. Prioritise readability and maintainability over architectural purity
  4. Feature cohesion > technical layer separation

Supporting Material