Glossary
Hexagonal architecture
The application is the centre of the system; its surroundings are replaceable ways to enter it or obtain a result from it.
Short definition
A port is an application contract; an adapter translates it for a specific environment.
Hexagonal architecture, or Ports and Adapters, describes an application as a core surrounded by interfaces. An inbound adapter accepts a stimulus from the environment and calls an inbound port. An outbound port, by contrast, expresses a capability the core needs from an external system; an outbound adapter supplies its concrete implementation.
The name does not mean six layers or a network TCP port. The hexagon is merely a drawing intended to prevent the assumption that an application must have one privileged entry point, such as an HTTP controller. A single use case can serve the web, command line, a message consumer, and a test without duplicating its rules in each one.
The problem it solves
Neither a technical input nor a service provider should dictate use-case rules.
The approach is valuable when the same application scenario needs multiple inputs, multiple implementations, or independent testing.
- an HTTP endpoint, CLI import, and message consumer over the same application operation
- choosing between an HTTP API, XML feed, and prepared test data for one information source
- separating the application task from a particular database client or message queue
- fast use-case tests without a network, file system, or real database
- gradually replacing an external provider without rewriting every place that uses its data
- aligning the meaning of an operation across an internal tool, public API, and batch processing
Practical example
Retrieving carrier pickup points
For checkout, the application needs to return pickup points for the selected carrier. The inbound ListCarrierPoints port expresses an operation available to the application environment. An HTTP controller can call it after a web request, while the same port can also serve an internal CLI availability-checking tool.
To load the data, the core requests the outbound CarrierPointSource contract. Production may use an HTTP API adapter, a legacy integration an XML adapter, and a test a simple fake with predefined points. The goal is not to hide every object behind an interface; the contract is useful because the source can vary and the application needs a stable meaning for the result.
Text diagram
HTTP controller / CLI příkaz
→ ListCarrierPoints (vstupní port a use case)
→ CarrierPointSource (výstupní port)
→ HTTP API adaptér | XML adaptér | testovací fake
→ seznam výdejních míst pro aplikaci
How it works
Two sides of interfaces around the application core
The text diagram is an alternative to the hexagon: several adapters can enter through the same port, and several others can serve a need of the core.
- Inbound adapter An HTTP controller, CLI command, or message consumer converts a particular transport into an application call. It validates the technical shape of the input and passes understandable data.
- Inbound port The port describes what the environment can ask the application to do. It may be an interface, public application service, or another explicit contract; it is neither a network port nor automatically one class.
- Application core The use case coordinates rules, determines the result, and understands the meaning of the operation. It should not be tied to whether it was started by HTTP, a scheduler, or an incoming message.
- Outbound port The core names a capability it needs from the environment: loading data, storing a change, sending a command, or obtaining the time. The contract should express the application need, not copy a provider API.
- Outbound adapter A database client, HTTP integration, file store, or fake translates the contract into a specific technology. The chosen implementation is connected when assembling the application.
Main components
Ports and adapters have different responsibilities.
The division does not prescribe a number of files. It is a way to identify which decisions belong to the application and which to a particular technology.
Inbound port
Gives the environment a stable way to invoke a use case. Its contract describes an application operation, such as creating an order or listing pickup points.
Inbound adapter
Connects a specific channel. A controller understands HTTP statuses and form data, a CLI adapter understands arguments, and a consumer understands message format; these details do not belong in the core.
Outbound port
A contract defined from the perspective of the application need. CarrierPointSource need not reveal whether it uses REST, XML, or a database when those differences are irrelevant to the use case.
Outbound adapter
Implements the port through a particular system. It may contain authentication, format conversion, retries, or mapping provider errors to meanings understood by the core.
Application composition
A concrete adapter should be selected at the edge of the system. Supplying dependencies to the use case allows the implementation to change in a test or an operational migration.
Benefits and limitations
Replaceability is a benefit only where change is genuinely expected.
Benefits
- one use case can serve multiple technical inputs
- external integrations and databases have a clearer place at the application edge
- tests can use controlled adapters without real infrastructure
- translation of provider errors and formats is concentrated outside application rules
Limitations and common mistakes
- creating a port for every internal class even when there is no alternative or boundary
- copying detailed DTOs from an external API throughout the application model
- treating an adapter as one mandatory class or a port as a network TCP port
- assuming the approach requires microservices, many layers, or separate deployment
Pragmatism
A direct service can be better than an abstraction without a reason.
If a simple administration action calls one stable framework component and will have no other input or implementation, an additional port and adapter may only lengthen the path to the result. In that case, direct use of a well-encapsulated service is legitimate. The added complexity becomes worthwhile when different sources, inputs, or tests genuinely need a stable boundary.
A good boundary can be described in the language of the application, such as “we need a source of pickup points,” rather than “we need a wrapper around the provider client.” If a contract merely repeats every call of one library, the core is probably not protected and only another interface has been added.
What to consider
A boundary should translate meaning, not merely relocate a framework API.
A concrete adapter can be complex, but its complexity should not leak needlessly into the use case.
- name the port after the capability the application actually needs
- keep HTTP, CLI, and message formats in inbound adapters
- translate provider errors and data formats into behaviour understood by the application
- use a test fake for decision logic and verify the production adapter separately at its boundary
- add new ports for real sources of change, not as a mechanical convention
Common questions
Hexagonal architecture in practice
Is a port in hexagonal architecture a network port?
No. It is an application contract at the system boundary. It can be expressed through an interface, application-service method, or another explicit form, but is unrelated to a TCP or UDP port.
Must every adapter be one class?
No. An adapter is the responsibility of translating between the application and a specific environment. It may contain a client, mapper, configuration, and several classes; a small adapter may instead be a single service.
Does hexagonal architecture require microservices?
No. It also suits one module of a modular monolith or a conventional web application. It says nothing about the number of deployed services, only the boundary between the application core and its environment.
When should a port not be added?
When there is no distinct application boundary or likely change in the input or output. A contract that merely wraps one stable internal class without a different meaning usually adds no value.
How I use architecture in practice
I use ports where technology should not dictate application meaning.
For integrations and multiple entry points, I separate application decisions from a specific HTTP interface, database, or queue. I size the boundary to the actual complexity.