Engineering note
Making dependency boundaries explicit
Application-owned contracts and explicit dependency direction.
Architecture / Dependency boundaries
Dependency ownership
Architectural boundaries depend on source references and responsibility allocation. Business rules, application orchestration and external integration should have explicit owners, supported by the dependency structure.
The projects provide two concrete separations: reusable framing shared by client connectors and server listeners, and upstream token strategies separated from caching and renewal coordination.
Proposed CLEAN structure
This proposed structure organises a modular gateway around domain rules, application operations and infrastructure adapters. It illustrates dependency direction rather than the projects' implemented assembly layout.
Source dependencies point towards the use case
- Presentation An API endpoint or operator action invokes an application operation.
- Application The use case coordinates the operation and owns the ports it requires.
- Domain Message rules and invariants remain independent of transport and storage frameworks.
Application-owned port
The independent C# example below defines a dispatch contract owned by the application layer.
using System;
using System.Threading;
using System.Threading.Tasks;
/// <summary>Application-owned dispatch boundary for an approved message.</summary>
public interface IApprovedMessageSender
{
/// <summary>
/// Sends an approved payload, completing when the adapter accepts the send.
/// Cancellation or transport failure is reported by the returned task.
/// Completion does not establish delivery to the final recipient.
/// </summary>
Task SendAsync(ReadOnlyMemory<byte> payload, CancellationToken cancellationToken);
}
A TCP adapter implements the port without exposing socket types to the use case. A test double can capture payloads or report failure. Task completion denotes acceptance by the adapter, not end-to-end delivery acknowledgement.
Dependency enforcement
In this proposed layout, application references domain, while adapters reference the contracts they implement. Compilation checks available references; a dependency check or architecture test is required to prevent prohibited project references being introduced.
Boundary verification should establish that use cases can be tested without a web host and that transport changes remain within the adapter. Framework types exposed through application contracts can undermine that separation.
Ports are appropriate at external dependencies that require application-level control. Separate representations are justified where their responsibilities or lifecycles differ.
Design restraint
A component should have a defined responsibility and clear ownership of state, failures and cleanup. A forwarding wrapper without an additional contract or policy introduces maintenance cost without necessarily improving separation.
Shared components require shared responsibilities. Superficial similarity alone is insufficient justification for coupling otherwise independent features through a common package.
Domain rules, application operations and transport adapters can remain within one process. Separate deployment requires an operational reason, such as independent scaling or ownership, and provision for the resulting failure and recovery behaviour.
Persistence and recovery requirements determine the appropriate mechanism. In-memory channels, message brokers and local file stores provide different guarantees and require verification against the intended use.
Examples are generalised or synthetic and omit operational details and identifying information.
← All engineering notes