Skip to main content

Hexagonal Architecture

Without structural boundaries, external dependencies will inevitably corrupt your core logic.

Hexagonal architecture (ports and adapters) isolates core business logic from external concerns. The domain doesn't know about databases, Next.js routers, AI LLMs, or Smart Contracts. It only knows about defined ports (interfaces) that adapters implement.

As we transition from Web2 logic to Phygital Agents and Web3 ledgers, treating these external systems as untrusted, swappable adapters becomes critical for system stability.

Dependency rule: Code flows strictly inward. The Domain Trust Architecture assumes the outside world is untrusted and potentially flawed. The domain depends on nothing. Adapters depend on ports. Ports define what the domain expects.


Nx Library Pattern

We map hexagonal layers to our standard Nx monorepo library structure:

libs/
agency/ ← Intelligence (Pure decision logic, workflows, scoring)
domain/ ← Pure types, interfaces, entities (No dependencies)
application/ ← Use cases, service coordination
app-server/ ← Server composition roots (per-app DI wiring)
app-client/ ← Client components (per-app UI)
data-repositories/ ← Drizzle DB implementations (Secondary Adapters)
infrastructure/ ← External service adapters (Resend, Clerk, LlamaCloud)
shared-utils/ ← Logger, pure helpers

apps/
dreamineering/ ← Web apps acting as thin shells
drmg-sales/ ← Next.js host

Layer Responsibilities

LayerPurposeDepends OnMaps To
DomainPure entities and rule contracts.Nothing.libs/domain/
IntelligenceDeterministic algorithms, pure math (SPCL, Explore-Exploit).Domain.libs/agency/
ApplicationUse-case orchestration.Domain, Intelligence.libs/application/
Primary AdaptersDriving input (UI, Server Actions, CLI, Webhooks).Application, Domain.libs/app-client/
Secondary AdaptersDriven output (DB, AI SDKs, Blockchains).Domain.libs/data-repositories/, libs/infrastructure/

Isolating AI Logic

Drawing from our Agency principles, we treat AI models as untrusted external actors within our exploration environment. Isolating agent business logic ensures our systems are scalable, adaptable, and resistant to hallucination entanglement. It enforces the separation between pure mathematical capabilities and actual agent decisions.

Conventional AI IntegrationHexagonal AI Pattern
Business logic parses raw LLM textDedicated adapter ensures Zod payload
Tests require fragile LLM mocksTests mock deterministic IGenerationPort
Deeply coupled to specific modelsSwappable AI providers via DI

Why Isolate AI?

  1. Unpredictable Output: LLMs hallucinate. If business logic relies directly on an LLM's raw output without a structural boundary, the system will crash.
  2. Model Swappability: Today we use OpenAI; tomorrow DeepSeek. The core application logic must not know the difference.
  3. Testability: Mocking an entire LLM for unit tests is slow. Mocking a defined IGenerationPort is instant.

Hexagonal AI Pattern

  • The Port: The domain defines exactly what the AI needs to return using strict interfaces (e.g., Zod schemas representing AIResponsePayload).
  • The Intelligence Core (libs/agency/): Houses pure, deterministic functions alongside the LLM coordination workflows. The AI agent accesses these algorithms (like SPCL Scoring or Optimal Stopping) via ports, meaning the agent can query the math, but the agent cannot corrupt the math.
  • The Middleware Adapter: The adapter (using the Vercel AI SDK) calls the LLM, catches unstructured errors, parses the JSON, and forces it through the Domain's Zod schema contract before the Domain ever sees it.

If the AI hallucinates, it fails at the Adapter boundary. The Domain remains pristine.


Web3 Integration

Like an AI model, a Blockchain is an external, slow, immutable database adapter.

Onchain Privacy Proofs

ZK-SNARKs enable privacy while maintaining verifiable truths. In a hexagonal setup:

  • The Domain defines rules for IdentityClaim validation.
  • The Adapter handles the heavy cryptographic math to verify the proof against the blockchain via RPC.
  • The Application layer asks the adapter: "Is this proof valid?" and receives a boolean. The domain logic remains completely isolated from the cryptographic mechanics.

Physical Infrastructure Networks

Physical networks (like robotic fleets) generate massive amounts of telemetry.

  • Secondary Adapters: Intercept the raw hardware IoT data streams and translate them into standardized Domain Events.
  • Intelligence Core: Runs our DePIN Scoring Algorithm across those events without needing to know which specific hardware device generated them.

Next.js Integration

In a Next.js environment, we use Server Actions as our Primary Adapters and wire our dependencies in a Composition Root.

Primary Server Adapter

// libs/app-server/src/actions/deal-action.ts
"use server";
import { runSalesForecast } from "@myorg/agency/sales-forecasting";
import { drizzleDealRepo } from "@myorg/data-repositories";
import { aiForecastAdapter } from "@myorg/infrastructure/ai";

export async function submitForecastAction(dealId: string) {
// Pass adapters into the pure agency function
return runSalesForecast(dealId, {
dealRepo: drizzleDealRepo,
aiOracle: aiForecastAdapter,
});
}

Port Contracts

// libs/domain/src/ports/ForecastingOracle.ts
export interface IForecastingOracle {
predictVelocity(signals: DealSignal[]): Promise<VelocityScore>;
}

Secondary AI Adapter

// libs/infrastructure/src/ai/openai-forecaster.ts
import type { IForecastingOracle } from "@myorg/domain";
import { generateObject } from "ai";

export const aiForecastAdapter: IForecastingOracle = {
async predictVelocity(signals) {
// LLM logic safely contained here. It MUST return the defined VelocityScore shape.
const { object } = await generateObject({
/* ... */
});
return object;
},
};

Nx Boundary Enforcement

To protect these boundaries, restrict imports using @nx/enforce-module-boundaries in .eslintrc.json:

{
"rules": {
"@nx/enforce-module-boundaries": [
"error",
{
"depConstraints": [
{ "sourceTag": "layer:domain", "onlyDependOnLibsWithTags": [] },
{ "sourceTag": "layer:agency", "onlyDependOnLibsWithTags": ["layer:domain"] },
{
"sourceTag": "layer:application",
"onlyDependOnLibsWithTags": ["layer:domain", "layer:agency"]
},
{ "sourceTag": "layer:infrastructure", "onlyDependOnLibsWithTags": ["layer:domain"] },
{ "sourceTag": "layer:app-server", "onlyDependOnLibsWithTags": ["*"] }
]
}
]
}
}

Now, Hexagonal violations fail at the build step before they reach production.

Context