Docs Usecase-Centric Design

Usecase-Centric Design

Many systems claim to be “layered”.

But when you look closer, they are often organized around:

  • Controllers
  • Services
  • Repositories
  • Framework abstractions

What is missing is the actual reason the system exists.

Plumego promotes a different organizing principle:

The system should be structured around use cases — the things the system does.

This document explains what usecase-centric design means in practice,
and why it dramatically improves clarity, evolution, and team alignment.


What Is a Usecase?

A usecase represents an intentional action the system can perform.

Examples:

  • Create an order
  • Cancel a subscription
  • Reset a password
  • Publish an event
  • Generate a report

A usecase answers:

  • Who can do this?
  • What happens when they do?
  • Which rules apply?
  • What can go wrong?

It is not a technical concept.
It is a behavioral contract.


The Problem with Layer-Centric Organization

A common project structure looks like this:


handlers/
services/
repositories/
models/

This structure answers:

  • “Where is the HTTP code?”
  • “Where is the database code?”

But it does not answer:

  • “Where is the logic for cancelling an order?”
  • “What happens when a user upgrades a plan?”

Behavior becomes scattered across layers and files.

Understanding a single business action requires jumping across the codebase.


Usecases as the System’s Backbone

In a usecase-centric design:

  • Each usecase is explicit
  • Each usecase has a clear entry point
  • Each usecase owns its flow
  • Each usecase coordinates domain logic

The system becomes a collection of named behaviors, not technical fragments.


A Typical Usecase Shape

A usecase usually consists of:

  • Input (explicit data)
  • Dependencies (interfaces)
  • Execution logic
  • Returned result or error

Example (conceptual):

type CancelOrderUsecase struct {
	repo OrderRepository
}

func (u *CancelOrderUsecase) Execute(input Input) error {
	order, err := u.repo.FindByID(input.OrderID)
	if err != nil {
		return err
	}

	return order.Cancel()
}

This code tells a complete story.


Usecase vs Domain Logic

A crucial distinction:

  • Domain defines what is always true
  • Usecase defines what happens in a specific scenario

The domain does not know why an operation is triggered.
The usecase does.

Usecases orchestrate domain behavior — they do not replace it.


Usecase-Centric Package Organization

A common Plumego-friendly structure:

internal/
  order/
    cancel/
      usecase.go
      input.go
      errors.go
    create/
      usecase.go
      input.go

Benefits:

  • All logic for a behavior lives together
  • Easy to navigate
  • Easy to review
  • Easy to evolve

This structure scales better than layer-based directories.


Handlers as Usecase Adapters

In a usecase-centric system:

  • Handlers adapt HTTP → usecase input
  • They do not contain business decisions
  • They delegate immediately

Example:

func CancelOrderHandler(ctx *plumego.Context) {
	input := parseInput(ctx)
	err := cancelOrderUsecase.Execute(input)
	handleResult(ctx, err)
}

Handlers become thin translators.


Repositories Serve Usecases, Not the Other Way Around

Repositories exist to support usecases.

They are not generic data access layers.

This implies:

  • Interfaces are defined by usecases
  • Repositories are injected
  • Different usecases may require different interfaces

Avoid “one repository to rule them all”.


Usecases and Authorization

Authorization decisions are often usecase-specific.

Example:

  • Who can cancel an order?
  • Under what conditions?

Embedding authorization in usecases makes these rules explicit and testable.


Usecases and Naming Discipline

Good usecase names are:

  • Verbs
  • Explicit
  • Domain-aligned

Prefer:

  • CreateOrder
  • CancelSubscription

Avoid:

  • OrderService
  • HandleOrder

Names communicate intent.


Usecase-Centric Testing

Usecases are ideal test units:

  • Dependencies can be mocked
  • Behavior is explicit
  • Error paths are clear

Most meaningful business tests live at the usecase level.


When Usecase-Centric Design Matters Most

This pattern is especially valuable when:

  • The system has non-trivial business rules
  • Multiple engineers collaborate
  • Features evolve over time
  • The codebase is expected to live for years

For trivial CRUD apps, it may feel heavy — and that is acceptable.


Common Anti-Patterns

God Services

Large “service” structs handling many unrelated behaviors.


Generic Repositories

Interfaces that expose too much and encourage misuse.


Usecases Hidden in Handlers

Business logic that never leaves the HTTP layer.


Summary

In Plumego:

  • Usecases are first-class citizens
  • System structure reflects behavior, not technology
  • Handlers adapt, usecases decide, domains enforce
  • Code tells stories, not just mechanics

A usecase-centric design makes systems easier to understand —
especially months or years after they are written.


Next

With usecases at the center, the final structural pattern is:

→ Dependency Wiring

This shows how to assemble all usecases, adapters, and infrastructure explicitly into a running system.