Three-Layer Design
Viaduct is built on a three-layer architecture that provides strong separation of concerns and enables different personas to work independently:Engine API
Core GraphQL execution with dynamic typing
Service API
Integration layer for infrastructure
Tenant API
Developer-facing API with static typing
Layer Responsibilities
Each layer has a distinct purpose and audience:| Layer | Purpose | Target Audience |
|---|---|---|
| Engine API | GraphQL query planning and execution | Framework contributors |
| Service API | Infrastructure integration (observability, security) | Service engineers |
| Tenant API | Business logic implementation | Application developers |
The architecture creates strong abstraction boundaries between layers, allowing the engine to evolve for performance improvements while the tenant API evolves independently for better developer experience.
Engine API
The Engine API is Viaduct’s core GraphQL execution layer. Located inengine/api and engine/runtime, it implements the fundamental GraphQL query planning and resolution algorithm.
Key Characteristics
Dynamic Typing
The engine works with dynamically-typed representations of GraphQL values. Input and output objects are represented as simple maps from field name to value, avoiding coupling to any specific type system.
Executor Interfaces
The engine defines executor interfaces like
FieldResolverExecutor and NodeResolverExecutor that the Tenant API implements. When resolving a field, the engine calls the executor’s batch resolution methods.Engine Interface
The primary entry point is theEngine interface:
engine/api/src/main/kotlin/viaduct/engine/api/Engine.kt
Executor Pattern
The engine invokes resolvers through executor interfaces. Here’s how field resolution works:The
TenantModuleBootstrapper interface connects the tenant layer to the engine by providing mappings from field coordinates to FieldResolverExecutor instances and from node types to NodeResolverExecutor instances.Service API
The Service API is the integration layer for service engineers who embed Viaduct into their organization’s infrastructure. Located inservice/api, service/runtime, and service/wiring.
Service Engineer Responsibilities
Service engineers integrate Viaduct with:- Web serving frameworks (Spring Boot, Ktor, etc.)
- Observability platforms (metrics, tracing, error reporting)
- Security infrastructure (authentication, authorization)
- Dependency injection frameworks
- Build and deployment pipelines
The Viaduct Object
TheViaduct interface is the main entry point for executing GraphQL operations:
service/api/src/main/kotlin/viaduct/service/api/Viaduct.kt
Building a Viaduct Instance
UseViaductBuilder for full control over SPI configuration:
Service Provider Interfaces (SPIs)
Viaduct uses SPIs for infrastructure integration. Key SPIs include:| SPI | Purpose |
|---|---|
ErrorReporter | Integrate with error logging infrastructure |
FlagManager | Feature flag integration |
GlobalIDCodec | Custom Global ID encoding/decoding |
ResolverErrorBuilder | Custom error response formatting |
Example: Web Controller Integration
Here’s how a Spring Boot controller integrates with Viaduct:Tenant API
The Tenant API is the developer-facing layer where application developers write business logic. Located intenant/api and tenant/runtime.
Key Characteristics
Static Typing
Unlike the engine’s dynamic representation, the Tenant API provides statically-typed Kotlin classes (GRTs) generated from your GraphQL schema.
Type-Safe Builders
Every GraphQL type gets a generated builder class with type-safe methods for constructing response objects.
Resolver Types
The Tenant API provides two primary resolver types:Generated Code
For each GraphQL type, Viaduct generates:- GRT (GraphQL Representational Type) - A value class with suspending getters
- Builder - A builder class for constructing instances
- Resolver base classes - Abstract classes that resolvers extend
User type:
Generated Code
Layer Boundaries
The boundaries between layers are carefully designed:Engine ↔ Tenant Boundary
TheTenantModuleBootstrapper interface bridges the two layers:
Service ↔ Engine Boundary
The Service API usesEngineFactory to create Engine instances with the appropriate configuration and bootstrappers.
Benefits of This Architecture
Independent Evolution
Each layer can evolve independently. Performance improvements to the engine don’t require changes to application code.
Clear Ownership
Different personas (framework contributors, service engineers, developers) have clearly defined responsibilities.
Type Safety
Developers work with type-safe generated code while the engine maintains flexibility with dynamic typing.
Testing Isolation
Each layer can be tested independently with well-defined interfaces between them.
See Also
- Central Schema - How teams contribute to a unified schema
- Tenant Modules - Building modular applications
- Generated Code - Understanding GRTs and compilation schemas