# Foundation 2. Vocabulary

## Design the vocabulary

A name does not decorate a finished solution. Decide what concept the consumer will see and what they can expect from it.

When an API uses words like `execute`, `data`, or `response`, it forces you to read the implementation to discover their meaning. When you name an intention, a result, or a recognizable situation, it allows you to understand the contract without opening up the implementation.

The vocabulary of an API should remain true even if its implementation changes.

Every value that crosses an API needs a clear meaning. Normally it is enough to answer these questions:

- what does it represent?
- what values ​​are valid?;
- how does it compare?
- can it be absent?;
- who preserves its mutable state?;
- What representation can the consumer observe?

The answer doesn't always require a new type. A proper type contributes when it names a difference, protects an invariant, or prevents an invalid combination. If the context already avoids confusion and the rules are the same, separating two values ​​only adds ceremony.

## Example: Add a task to a queue

This signature mainly describes the organization of the code:

```text
QueueService.Execute(EnqueueCommand) → QueueResponse
```

We don't know what is executed, what the response contains, or what can go wrong. The same capacity can be expressed like this:

```text
Queue.Enqueue(Task) → Position | QueueFull
```

Each word fulfills a function:

- `Queue` names the module and provides context;
- `Enqueue` expresses the consumer's intention;
- `Task` names the value that is entered;
- `Position` explains the result;
- `QueueFull` identifies a situation in which the consumer can act.

The implementation can use memory, files, or a remote system. None of these decisions require changing the message.

## Example in Go

The example keeps `Queue`, `Enqueue`, `Task`, `Position`, and `QueueFull` as visible vocabulary.

```go
import "errors"

type Task struct{ ID string }
type Position int

var ErrQueueFull = errors.New("queue full")

type Queue interface {
    Enqueue(Task) (Position, error)
}
```

## Exported names

Public names are the vocabulary shared between a module and its consumers. They deserve more stability than internal identifiers because they appear in calls, documentation, tests, and sometimes serialized data.

Each language expresses that border in another way. In Go, an exported identifier begins with a capital letter; lowercase names remain within the package. A `internal` directory also allows you to limit which part of the tree a package can import.

Exporting does not improve a name or turn a type into a good abstraction. First, what the consumer needs to name is identified; then the corresponding visibility is given. Helpers and internal representations can use more technical words without contaminating the public conversation.

## Good names

### The module

The name of a module should indicate the capacity it has, not the pattern with which it was built.

`Manager`, `Service`, `Handler`, `Facade`, or `Helper` are usually weak when appearing alone. They classify a technical structure, but do not explain what it offers. `QueueManager`, for example, allows you to imagine almost any responsibility; `Queue` sets a specific context for messages such as `Enqueue`, `Next`, or `Remove`.

This does not make technical names a universal mistake. In composition it may be useful to distinguish `MemoryQueue` from `RemoteQueue`, because there the consumer chooses an implementation. The API they both provide can still be called `Queue`.

The useful question is: does the consumer need to know about this mechanism? If the answer is no, the technical name belongs to the interior.

### The messages

Messages express intentions or facts, not internal steps.

A verb like `Enqueue` allows you to anticipate the effect. `Run`, `Execute`, `Process`, or `Handle` are only accurate when executing, processing, or dispatching is actually the capability of the module.

The simplest test is to read the usage as a sentence:

```text
position = queue.Enqueue(task)
```

If to understand it you have to translate framework categories, the contract still speaks from the implementation.

### Values ​​and results

A type deserves a name when it represents a difference that matters. `Task`, `Position`, and `Capacity` say more than `Data`, `Item`, or `ValueObject`, as long as those are the words in the actual context.

Suffixes that repeat the technical category are usually superfluous:

```text
TaskModel
PositionValueObject
QueueResponseDTO
```

They may be needed within an adapter that translates two representations, but they should not be propagated to the main API by accident.

Units are also part of the meaning. `Delay` may be ambiguous if the consumer needs to distinguish milliseconds from seconds. A longer name is not always necessary; Yes, it is necessary to preserve the difference that prevents incorrect use.

### Booleans, errors and events

A boolean is best understood as a proposition:

```text
queue.IsFull()
queue.Contains(taskID)
```

`Check`, `Flag`, or `Status` do not clarify what `true` means. If there are more than two relevant results, the contract probably needs a named state instead of a boolean.

Public errors describe situations to which the consumer can react. `QueueFull` allows you to wait or choose another queue. `DatabaseError` leaks infrastructure and may not offer any useful decisions. Technical details can be preserved as internal cause, log or diagnosis without becoming stable vocabulary.

An event names an event that has already occurred. `TaskQueued` avoids confusing the notification with the `EnqueueTask` request. The past also helps the name remain true even if the transportation changes.

## Communication

Vocabulary works as a system, not as a list of isolated terms. Module, message, arguments and results must be able to be read together as a conversation.

### Context avoids repetitions

Precision is not about including the entire explanation in each identifier:

```text
queue.Enqueue(task)
```

It is clearer that:

```text
queueService.ExecuteTaskEnqueueCommand(taskValueObject)
```

In the first case, module, message and argument distribute the meaning. In the second, the technical categories add length without better explaining the contract.

A short name can be ambiguous and a long one can compensate for a poorly chosen context. The goal is to use minimal words that preserve the meaning where they are read.

### Find a name

The name usually appears when first describing the conversation:

1. Write a sentence from the consumer: "I want to add this task to the queue."
2. Separate the context, intention, values ​​and situations that change behavior.
3. Check what words people who know the problem use.
4. Read an entire call and remove what the context already says.
5. Imagine another implementation and check that the vocabulary is still true.
6. Review each error or event that the consumer should distinguish.

If it is difficult to name a module, there may be mixed responsibilities or premature abstraction. In that case, it is advisable to return to the conversation before looking for a more elegant synonym.

This practice coincides with the ubiquitous language of DDD: names gain precision within an explicit context, not in a universal dictionary ([*DDD Reference*, Eric Evans](https://www.domainlanguage.com/wp-content/uploads/2016/05/DDD_Reference_2015-03.pdf)). API-DD adds a concrete check: the vocabulary must work from the consuming module and survive alternative implementations.

## Renaming is migrating

Once published, a word is part of the contract. Changing it may break compilation, serialized messages, documentation, metrics, or integrations.

A migration may require entering the new name next to the old one, adapting consumers, and removing the alias when the dependency no longer exists. Just because the new name is better does not make the change harmless.

> **A good name explains the conversation and does not reveal the mechanism that makes it possible.**
