Docs Domain and Usecase

Domain and Usecase

In long-lived systems, most architectural decay happens inside the core.

Not in frameworks.
Not in infrastructure.

But in the blurred boundary between what the system is and what the system does.

Plumego strongly encourages a clear separation between:

  • Domain — what the system is
  • Usecase (Application) — what the system does

This document explains how to structure that separation in practice.


Domain: What the System Is

The domain layer represents the stable core of the system.

It models:

  • Core business concepts
  • Invariants and rules
  • Behaviors that define correctness

Typical domain elements

  • Entities
  • Value objects
  • Domain services
  • Domain errors

Example (conceptual):

type Order struct {
	ID     OrderID
	Items  []Item
	Status Status
}

func (o *Order) Cancel() error {
	if o.Status == Shipped {
		return ErrCannotCancel
	}
	o.Status = Cancelled
	return nil
}

The domain expresses rules, not workflows.


Domain Characteristics

A well-designed domain layer is:

  • Stable over time
  • Independent of transport
  • Independent of frameworks
  • Independent of infrastructure

The domain must not know:

  • How a request arrived
  • Who initiated the action
  • Which database is used
  • Which framework is running

If it does, it is no longer a domain.


Usecase: What the System Does

The usecase (application) layer represents behavior.

It answers questions like:

  • What happens when a user places an order?
  • How do multiple domain objects interact?
  • Which operations are allowed in which scenarios?

Usecases orchestrate domain objects to achieve a goal.

Example (conceptual):

type PlaceOrderUsecase struct {
	repo OrderRepository
}

func (u *PlaceOrderUsecase) Execute(input PlaceOrderInput) error {
	order := NewOrder(input.Items)

	if err := u.repo.Save(order); err != nil {
		return err
	}

	return nil
}

The usecase defines intent and flow, not core rules.


Key Differences at a Glance

AspectDomainUsecase
PurposeDefine correctnessCoordinate behavior
StabilityVery highMedium
Knows about workflowsNoYes
Knows about persistenceNoVia interfaces
Knows about HTTP / PlumegoNeverNever

Both layers are framework-agnostic.


Dependency Direction

The dependency rule is strict:

Usecase → Domain
  • Domain must not depend on Usecase
  • Usecase may depend on Domain
  • Neither depends on Plumego or HTTP

Infrastructure implements interfaces defined by Usecase or Domain.


Where Interfaces Belong

Interfaces should be owned by the layer that depends on them.

Example:

// In usecase layer
type OrderRepository interface {
	Save(*Order) error
}

Infrastructure implements this interface:

// In infra/mysql
type OrderRepository struct {}

func (r *OrderRepository) Save(o *Order) error {
	// persist to DB
}

This keeps control in the core, not the edges.


Usecase Inputs and Outputs

Usecases should define explicit input and output types.

Avoid passing:

  • HTTP-specific types
  • Context objects
  • Framework structs

Prefer simple, explicit data:

type PlaceOrderInput struct {
	UserID string
	Items  []ItemInput
}

This makes usecases:

  • Easy to test
  • Reusable across transports
  • Clear in intent

Error Responsibility

  • Domain errors represent rule violations
  • Usecase errors represent failure to complete an operation

Neither layer decides:

  • HTTP status codes
  • Response formats
  • Error serialization

Those decisions belong to handlers.


Common Anti-Patterns

Fat Usecases

Usecases that contain core business rules usually indicate:

  • Domain logic was never extracted
  • Rules are duplicated
  • Stability is compromised

If a rule must be correct everywhere, it belongs in the domain.


Anemic Domain

A domain that only contains data structures, with all logic in usecases, is fragile.

This often leads to:

  • Duplicated rules
  • Inconsistent behavior
  • Hard-to-maintain systems

The domain should do things, not just hold data.


Infrastructure-Driven Domain

If database schemas define your domain model, boundaries are reversed.

Domain should shape persistence — not the other way around.


Testing Implications

With proper separation:

  • Domain can be tested without mocks
  • Usecases can be tested with stubbed interfaces
  • Handlers can be tested independently

This layered testability is one of the main benefits of Plumego’s architecture guidance.


Why Plumego Emphasizes This Separation

Plumego deliberately avoids:

  • Auto-wiring
  • Active Record patterns
  • Framework-driven models

Because these tend to blur domain and application concerns.

Plumego’s thin framework edge makes it natural — and necessary —
to define domain and usecase layers explicitly.


Summary

In a Plumego system:

  • Domain defines what must always be true
  • Usecase defines what the system does
  • Dependencies point inward
  • Frameworks stay at the edge
  • Infrastructure remains replaceable

This separation is the foundation of long-term maintainability.


Next

With domain and usecase clarified, the next architectural topic is:

→ Dependency Direction

This explains how to keep dependency flow correct as systems grow.