Discovering Domain Concepts and Behaviors

Turn use cases and domain language into a small, behavior-rich model. Learn how to find candidates, protect invariants, and avoid making every noun a class.

published: reading time: 14 min read author: GeekWorkBench
Quick Summary

Discover domain concepts by tracing use cases, clarifying stakeholder language, and stating the invariants each workflow must protect. The booking example shows how identity, value objects, lifecycle behavior, availability checks, payment, and concurrency boundaries can emerge from requirements without turning every noun into a class. Use the discovery questions, trade-off table, and failure scenarios to decide where rules belong and test a proposed model against realistic paths.

Discovering Domain Concepts and Behaviors

Introduction

When a feature request says “a customer books a room,” it gives you a useful starting point, but it does not tell you which objects to create. Copying every noun into a class and every verb into a method often produces a model that resembles the sentence while missing the rules that make the workflow correct.

Domain modeling is an investigation. Use cases show what people need to accomplish; conversations with domain experts reveal what the words mean and which rules cannot be broken. From that evidence, propose concepts, behaviors, invariants, and collaborations, then test the model against more scenarios. Some candidates become objects. Others remain values, policies, application services, or plain data.

A Practical Discovery Loop

Start with a concrete use case, such as “a guest books an available room for a date range.” Write down the successful path and the decisions the system must make. Then ask what can go wrong: the room is already booked, the range is invalid, or a payment is declined. These alternatives expose domain rules that the happy path hides.

For each step, ask:

  • What information must be known to make this decision?
  • Which rule decides whether the operation is valid?
  • What state changes if it succeeds?
  • Which other concepts must collaborate, and why?
  • Which terms do domain experts use, and do they mean the same thing in this workflow?

Treat a candidate model as a hypothesis. A useful concept explains one or more decisions in the use cases. If the name only mirrors a database column or UI control, it may not belong in the domain model.

Read Language for Meaning, Not Just Nouns

Highlight nouns, verbs, conditions, and state changes in requirements. Each is a clue, not a class specification. “Guest” may be an entity because identity and booking history matter over time. “Date range” may be a value object whose equality depends on its start and end. “Available” is likely a decision or state, not a standalone object. “Book” is behavior whose owner depends on the rules and data it needs.

Ask a domain expert to clarify ambiguous words. In a hotel, “room” could mean a physical room, a sellable room type, or a nightly inventory unit. Those meanings can differ in the same system. Preserve the distinction in names where it changes behavior; do not force one universal model across every team or workflow.

Find the Rules Before the Classes

An invariant is a rule that must remain true whenever the model is in a valid state. Write it as a sentence that can be checked. For example: “A confirmed booking has a valid date range and refers to a room allocation that does not overlap another confirmed booking.” This is more useful than an early diagram containing Guest, Room, and Booking with no stated responsibilities.

Some rules belong inside an object that can protect its own state. Other rules span multiple records or depend on current availability, so they need a domain policy, repository-backed check, or transaction boundary. Mark the boundary explicitly rather than pretending a single in-memory object can guarantee a system-wide fact under concurrent requests.

Map Collaborations Around a Use Case

Write the sequence of decisions and state changes in everyday language. Draw only the collaborators needed to explain that sequence. If the diagram needs a box for every technical layer, it is probably describing the application architecture rather than the domain model.

flowchart LR
    Guest[Guest] -->|requests dates| BookingService[Booking use case]
    BookingService -->|checks range| DateRange[DateRange]
    BookingService -->|asks about availability| RoomInventory[Room inventory]
    BookingService -->|creates valid booking| Booking[Booking]
    Booking -->|records| Room[Room allocation]
    BookingService -->|requests payment| Payment[Payment service]

This sketch is connected because every edge describes a collaboration in the booking scenario. It leaves persistence and HTTP out: those are implementation details unless a domain rule depends on them.

Work Through a Booking Example

Suppose the first requirement is: “A guest can book a room from a check-in date to a check-out date if the room is available. A booking must have at least one night.” The words suggest candidates, but the conditions narrow the model:

  1. Guest has identity; a guest can make multiple bookings.
  2. DateRange is a value. It can reject a checkout date that is not after check-in.
  3. Booking has identity and a lifecycle, such as requested, confirmed, and cancelled.
  4. Room availability is a decision over existing allocations and the requested range.
  5. The booking use case coordinates availability, booking creation, and payment, while the rule for a valid range stays near DateRange.

Here is a small Java sketch. It models the range invariant locally and keeps the availability query in an application-facing collaborator because it depends on stored bookings.

import java.time.LocalDate;
import java.util.UUID;

record DateRange(LocalDate checkIn, LocalDate checkOut) {
    DateRange {
        if (checkIn == null || checkOut == null || !checkOut.isAfter(checkIn)) {
            throw new IllegalArgumentException("check-out must be after check-in");
        }
    }

    boolean overlaps(DateRange other) {
        return checkIn.isBefore(other.checkOut()) && other.checkIn().isBefore(checkOut);
    }
}

final class Booking {
    private final UUID id;
    private final UUID guestId;
    private final UUID roomId;
    private final DateRange stay;
    private Status status;

    Booking(UUID guestId, UUID roomId, DateRange stay) {
        this.id = UUID.randomUUID();
        this.guestId = guestId;
        this.roomId = roomId;
        this.stay = stay;
        this.status = Status.REQUESTED;
    }

    void confirm() {
        if (status != Status.REQUESTED) {
            throw new IllegalStateException("only a requested booking can be confirmed");
        }
        status = Status.CONFIRMED;
    }

    enum Status { REQUESTED, CONFIRMED, CANCELLED }
}

The range check belongs in DateRange because every caller should get the same definition of a valid stay. The overlap rule uses half-open intervals: a guest checking out on June 10 does not overlap a new guest checking in on June 10. That convention should be confirmed with the business; otherwise, a clean implementation can still encode the wrong policy.

The Booking protects one local lifecycle rule. It cannot alone guarantee that two concurrent requests do not reserve the same room. The use case should check availability and persist the reservation under a transaction or other concurrency control appropriate to the storage system. Model boundaries clarify where the guarantee comes from; they do not make races disappear.

interface BookingRepository {
    boolean hasConfirmedOverlap(UUID roomId, DateRange stay);
    void save(Booking booking);
}

final class RequestBooking {
    private final BookingRepository bookings;

    RequestBooking(BookingRepository bookings) {
        this.bookings = bookings;
    }

    Booking request(UUID guestId, UUID roomId, DateRange stay) {
        if (bookings.hasConfirmedOverlap(roomId, stay)) {
            throw new IllegalStateException("room is unavailable for those dates");
        }
        Booking booking = new Booking(guestId, roomId, stay);
        bookings.save(booking);
        return booking;
    }
}

This is still a sketch. A production flow would define how payment authorization relates to confirmation, what happens when persistence succeeds but a payment call times out, and how duplicate client retries behave. Those questions can reveal new concepts such as a hold, payment attempt, or idempotency key. Add them when a use case or invariant needs them, not because the nouns sound plausible.

Candidate Objects, Behaviors, and Invariants

An object earns a place in the model when it helps express domain meaning or enforce a rule. Use these signals during discovery:

  • Identity over time: Is it important to recognize this same thing after its details change? A booking usually has identity; a date range usually does not.
  • Meaningful state transitions: Can it move through named states with rules? A booking can be requested, confirmed, or cancelled.
  • Rules tied to information: Does a concept have the data needed to validate or calculate something? A date range can check overlap; a booking can guard its transition.
  • Domain vocabulary: Do stakeholders use this term consistently for a concept that affects decisions?
  • Collaboration: Does a use case need this concept to ask a question, make a decision, or record a change?

These are prompts, not a scoring formula. A noun can be a field, a value, a role, a policy, a boundary, or irrelevant UI wording. A verb can become a method, a use case, a domain event, or an external integration call. Model what the rules require.

When to Use

Use this discovery approach when:

  • A feature has several business rules that are easy to contradict across endpoints.
  • Stakeholders use domain terms whose meanings need clarification.
  • A workflow changes state and invalid transitions would cause real business errors.
  • A team is deciding where responsibilities belong before implementing a feature.
  • Existing code has many conditional branches and duplicated policy checks.

For a small CRUD feature with stable fields and little domain behavior, a simpler request/response model may be enough. Start with the complexity you have evidence for.

When NOT to Use

Do not force a rich object model onto every application. A static content page, straightforward import/export utility, or thin integration may have no meaningful domain behavior to encapsulate. A data pipeline may be clearer as transformations over records. A functional design can model the same concepts and invariants without classes.

Also avoid using modeling workshops to delay a small experiment. If the uncertainty is cheap to resolve in code, build a narrow vertical slice, observe where rules land, and revise the model. The goal is a shared, useful understanding, not a perfect diagram before implementation.

Production Failure Scenarios

Two Guests Reserve the Same Room

Both requests check availability before either writes a booking. Each sees an empty range and saves a confirmation. The object model correctly validates each request in isolation, but the invariant spans concurrent transactions. Use an atomic reservation operation, exclusion constraint, lock, or another datastore-supported guarantee, then test contention at the persistence boundary.

Check-In and Check-Out Semantics Drift

The booking service treats checkout as exclusive, while a reporting query treats both endpoints as occupied nights. Adjacent stays appear to overlap in one flow but not another. Put the interval convention in the domain vocabulary and share the overlap rule or contract across the paths that need it.

Payment Timeout Leaves an Unknown Outcome

The provider may have authorized payment even though the caller timed out. Retrying as a fresh booking can charge twice or create conflicting reservations. Give payment attempts their own identity, use provider idempotency where available, and represent uncertain outcomes explicitly instead of treating a timeout as a definite failure.

Cancellation Releases Inventory Too Early

A request handler marks a booking cancelled before the payment refund or policy check completes. Inventory appears available while the business still considers the reservation active. Model the cancellation transition and its prerequisites, then make the workflow recoverable if an external step fails.

Trade-Off Table

Modeling choice Helps when Cost or risk
Rich domain objects Rules belong with state and must hold across many callers More types and navigation than simple data flows need
An application service A use case coordinates storage, policies, and external systems It can become a dumping ground if domain decisions move into it
Value objects A concept is defined by values and has reusable validation Excessive wrappers can obscure simple primitive data
A domain policy/service A rule needs several concepts or varies by policy The rule may be harder to find if the service name is vague
A diagram before code Collaborations are unclear across people or boundaries Diagrams can become stale or create false certainty

Observability Checklist

Discovery should include how operators will tell whether the modeled workflow is behaving correctly. For booking:

  • Record a correlation ID and booking ID across availability, reservation, and payment steps.
  • Measure booking attempts, confirmations, cancellations, and availability conflicts separately.
  • Track time spent in pending or uncertain states and alert on bookings that exceed the expected window.
  • Log transition names and outcome codes without exposing guest personal details.
  • Include a safe metric for overlapping reservation conflicts so concurrency defects surface quickly.
  • Keep business events stable enough to support support-team investigations and reconciliation.

Security and Compliance Notes

The model should not assume that possession of a booking ID grants access. Authorize the guest or staff actor at the application boundary, and check access again for administrative actions. Validate dates and room identifiers on the server; client validation improves feedback but does not enforce a domain rule.

Store only guest data required for the booking. Avoid placing email addresses, payment details, or access tokens in logs and traces. Keep payment card handling with a compliant provider, and make retention, deletion, and audit requirements explicit with the product and compliance owners. A domain model can make these responsibilities visible, but does not by itself establish compliance.

Common Pitfalls / Anti-Patterns

  • Noun extraction as design: Turning every noun into a class ignores meaning, behavior, and lifecycle.
  • Verb extraction as method lists: A verb can describe an entire use case or external action, not necessarily a method on the nearest noun.
  • One giant “domain service”: This collects rules in a procedural object and leaves the model unable to protect its own state.
  • Anemic objects by default: Data holders with unrestricted setters allow callers to bypass invariants.
  • Invented abstractions: BookingCoordinatorFactoryManager is not a domain concept because it has a long name.
  • Modeling database structure as domain truth: Tables and ORM relations are persistence choices; they may not capture business meaning.
  • Confusing local and global guarantees: An object can validate its own state, but uniqueness across concurrent requests needs an atomic boundary.
  • Treating the first vocabulary as permanent: Domain language evolves as the team learns; revise names when stakeholders correct them.

For the follow-up question of who should own the behavior once a candidate is identified, see GRASP responsibility assignment. For a broader discussion of where domain models apply across larger systems, see defining service boundaries.

Quick Recap Checklist

  • Start from a real use case, including failure paths.
  • Ask stakeholders what important terms mean in this workflow.
  • Write invariants as statements that can be checked.
  • Propose the smallest set of concepts that explains the decisions and state changes.
  • Assign behavior according to information, responsibility, and boundary constraints.
  • Separate local object rules from rules that need a transaction or external coordination.
  • Try the model against another scenario and change it when the language or rules disagree.
  • Keep a noun out of the class model when it adds no useful behavior or meaning.

Interview Questions

1. Why is noun extraction insufficient for discovering domain objects?

A noun only suggests a possible concept. It may represent a value, a field, a UI label, an external actor, or a concept with identity and behavior. Check whether the term has domain meaning, participates in a decision, owns state, or helps enforce an invariant before creating a class.

2. Where should an invariant be enforced?

Enforce a rule at the narrowest boundary that has enough information and authority to keep it true. A date range can reject an invalid interval itself. Preventing overlapping reservations across concurrent requests needs an atomic storage or coordination boundary as well as clear domain language.

3. How do you decide whether a behavior belongs in an entity or a service?

Start with the information the behavior needs and the rule it protects. If one entity owns the state and can enforce the rule coherently, an entity method is a strong candidate. If the decision spans concepts, varies by policy, or coordinates persistence and external systems, a policy or application service may fit better. Recheck cohesion and dependencies after assigning it.

Further Reading

Conclusion

Discover a domain model by following use cases, clarifying terms, and writing down the rules that must stay true. Candidate nouns and verbs help you ask better questions; they do not dictate your classes. Keep behavior near the state it protects when that makes the rule clear, and use services or transactional boundaries for coordination and guarantees that span objects. Then test the model against success, failure, and concurrency scenarios.

Category

Related Posts

GRASP: Assigning Object Responsibilities

Learn all nine GRASP patterns through a Java order workflow, with code, trade-offs, failure cases, and design review questions for intermediate developers.

#object-oriented-design #grasp #responsibility-assignment

Value Objects, Entities, and Aggregates

Learn how identity, value equality, and aggregate boundaries shape a reliable order model, with Java code, trade-offs, failure cases, and design checks.

#object-oriented-design #domain-driven-design #value-objects

Behavioral Patterns: Organize Collaboration

Compare all eleven GoF behavioral patterns by the change they isolate, the coupling they reduce, and the runtime costs they add to a system in real code.

#design-patterns #object-oriented-design #behavioral-patterns