ISSUE 42
Software Architecture

One Port, Three Client Crates: Building a Hexagonal Rust Ingester

An exploration of using hexagonal architecture in Rust to decouple network ingestion from business logic, enabling multiple transport protocols.

Abhik Kumar Panda
Abhik Kumar Panda
Creator & Engineer
August 28, 2026 · 3 min read
A diagram showing a central domain core surrounded by three distinct adapter crates for network ingestion.

In distributed systems, the coupling between transport-layer concerns and business logic is a frequent source of technical debt. When an ingester needs to support multiple protocols—such as gRPC, HTTP/1.1, and raw TCP streams—the naive approach often involves leaking transport-specific types into the core domain. By adopting a hexagonal architecture (or ports and adapters pattern) in Rust, we can maintain a clean separation of concerns, ensuring that the domain remains agnostic of the transport mechanism.

This article explores the implementation of a single, unified ingestion core in Rust that serves as the ‘domain’ center, while three distinct client crates serve as the ‘adapters’ that normalize incoming data into a common message format.

Defining the Domain Core

The core of our system is a library crate that defines the domain model and the trait-based ports for ingestion. The crucial step here is to define an ‘IngestionPort’ trait that abstracts the delivery of messages. The core does not know about sockets, request headers, or serialization formats; it only cares about receiving a validated domain object.

pub trait MessageIngestor: Send + Sync {
    fn ingest(&self, msg: DomainMessage) -> Result<(), IngestionError>;
}

pub struct IngestionService<T: MessageIngestor> {
    handler: T,
}

impl<T: MessageIngestor> IngestionService<T> {
    pub fn process(&self, raw: Vec<u8>) -> Result<(), IngestionError> {
        let msg = DomainMessage::try_from(raw)?;
        self.handler.ingest(msg)
    }
}

Implementing the Adapters

With the core defined, we can build independent client crates. Each crate is responsible for the unique idiosyncrasies of its protocol. For instance, the HTTP adapter manages connection pooling and header parsing, while the gRPC adapter handles the protobuf-to-domain mapping. Because these crates depend only on the core library, they remain lightweight and easily swappable.

The gRPC Adapter

The gRPC adapter acts as a shim that translates incoming protobuf messages into our domain-defined structures. By isolating this logic, we ensure that if our protobuf schema evolves, the blast radius is confined to this specific crate.

pub struct GrpcAdapter { service: IngestionService };

#[tonic::async_trait]
impl proto::Ingestor for GrpcAdapter {
    async fn send(&self, req: Request<proto::Message>) -> Result<Response<()>, Status> {
        let domain_msg = req.into_inner().into();
        self.service.process(domain_msg).map_err(|_| Status::internal("failed"))?;
        Ok(Response::new(()))
    }
}

Trade-offs and Considerations

While this architecture provides excellent decoupling, it introduces a degree of ceremony. Developers must maintain mapping layers between external protocol types and internal domain types. This ‘mapping tax’ is the price paid for long-term maintainability.

  • Increased boilerplate due to explicit type conversion.
  • Easier unit testing of business logic without mocking network stacks.
  • Ability to swap transport layers without altering the domain core.
  • Potential performance overhead from extra allocations during mapping.

Conclusion

Hexagonal architecture is a powerful tool for Rust systems that must evolve alongside changing infrastructure requirements. By strictly defining the boundaries between the transport adapters and the domain core, we create a robust system where the core logic remains protected from the volatility of external APIs. While it requires more upfront design, the payoff in testability and modularity is significant for any non-trivial ingestion pipeline.

Share Twitter LinkedIn
Abhik Kumar Panda
CONTRIBUTING FELLOW

Abhik Kumar Panda

Creator & Engineer

Software engineer and creator passionate about technical writing, systems architecture, and AI.

Continue Reading

Stay in the loop.

Get new engineering articles, systems ideas, and things worth knowing — straight to your inbox.

No spam. Just useful things.
Unsubscribe anytime.