graphql-rs

Domain Model Design: Visual GraphQL Architecture

This guide shows how we use Domain-Driven Design (DDD) to build a clean, maintainable GraphQL server architecture with visual diagrams and practical examples.

πŸ—οΈ How Our GraphQL Server is Organized (High-Level View)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    GraphQL Request Flow                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 1. HTTP Request β†’ Presentation Layer (GraphQL endpoint)        β”‚
β”‚ 2. Parse & Validate β†’ Application Layer (Use cases)            β”‚
β”‚ 3. Execute Query β†’ Domain Layer (Business logic)               β”‚
β”‚ 4. Fetch Data β†’ Infrastructure Layer (Resolvers, DB)           β”‚
β”‚ 5. Return Response ← All layers collaborate                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

🧱 DDD Architecture Layers (Visual)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  HTTP requests, GraphQL over HTTP
β”‚ Presentation Layer  β”‚  ← Controllers, GraphQL endpoints
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Application Layer   β”‚  ← Use cases, orchestration
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Domain Layer        β”‚  ← Business logic, entities, rules
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Infrastructure      β”‚  ← Data access, external services
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

🎯 Practical Example: How a Query Flows Through the System

Query: { user(id: "123") { name, posts { title } } }

1. Presentation Layer
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚ GraphQL Handler β”‚ ← Receives HTTP POST with query
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚
2. Application Layer
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚ ExecuteQuery    β”‚ ← Parses, validates, orchestrates
   β”‚ UseCase         β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚
3. Domain Layer
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚ QueryExecutor   β”‚ ← Applies business rules, validation
   β”‚ SchemaValidator β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚
4. Infrastructure Layer
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚ UserResolver    β”‚ ← Fetches data from database
   β”‚ PostResolver    β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Domain-Driven Design Overview

DDD focuses on modeling the business domain and organizing code around domain concepts rather than technical concerns. This approach helps create more maintainable and understandable software.

πŸ”§ Core DDD Concepts (With GraphQL Examples)

  1. Domain: GraphQL execution and schema management
  2. Ubiquitous Language: β€œQuery”, β€œSchema”, β€œResolver”, β€œField” (shared by all team members)
  3. Bounded Context: GraphQL Engine (separate from Auth, User Management, etc.)
  4. Entities: Schema, Query (have identity and lifecycle)
  5. Value Objects: Field, TypeDefinition (no identity, just data)
  6. Aggregates: SchemaAggregate (consistency boundary around Schema + Resolvers)
  7. Domain Services: QueryValidator, SchemaValidator (business operations)
  8. Repository: SchemaRepository (data access abstraction)

GraphQL Domain Model

🎨 Domain Model Visual Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     GraphQL Domain Model                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      contains      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”            β”‚
β”‚  β”‚   Schema    β”‚ ─────────────────▢ β”‚ TypeDef     β”‚            β”‚
β”‚  β”‚ (Entity)    β”‚                    β”‚ (Value Obj) β”‚            β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜            β”‚
β”‚         β”‚                                   β”‚                  β”‚
β”‚         β”‚ validates                         β”‚ describes        β”‚
β”‚         β–Ό                                   β–Ό                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      executes      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”            β”‚
β”‚  β”‚    Query    β”‚ ─────────────────▢ β”‚ Selection   β”‚            β”‚
β”‚  β”‚ (Entity)    β”‚                    β”‚ (Value Obj) β”‚            β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜            β”‚
β”‚         β”‚                                                      β”‚
β”‚         β”‚ managed by                                           β”‚
β”‚         β–Ό                                                      β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                              β”‚
β”‚  β”‚Schema       β”‚  ← Aggregate Root                            β”‚
β”‚  β”‚Aggregate    β”‚    (Consistency boundary)                    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                              β”‚
β”‚                                                               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ”„ How Domain Objects Interact (Request Flow)

1. Client Query Request
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚   Query     β”‚ ← "{ user { name } }"
   β”‚  (Entity)   β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚ passed to
           β–Ό
2. Schema Validation
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚   Schema    β”‚ ← Has TypeDefinitions, validates query
   β”‚  (Entity)   β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚ if valid
           β–Ό
3. Execution Planning
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚ Selection   β”‚ ← Breaks query into field selections
   β”‚(Value Obj)  β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚ executed by
           β–Ό
4. Aggregate Coordination
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚Schema       β”‚ ← Orchestrates validation + execution
   β”‚Aggregate    β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Core Bounded Context: GraphQL Execution Engine

Our primary bounded context encompasses the GraphQL specification implementation.

Domain Entities

Schema
pub struct Schema {
    id: SchemaId,
    version: SchemaVersion,
    types: HashMap<TypeName, TypeDefinition>,
    query_type: ObjectType,
    mutation_type: Option<ObjectType>,
    subscription_type: Option<ObjectType>,
}
Query
pub struct Query {
    id: QueryId,
    document: Document,
    operation_name: Option<String>,
    variables: Variables,
    validation_result: ValidationResult,
}

Value Objects

TypeDefinition

Represents GraphQL type information without identity:

#[derive(Clone, PartialEq)]
pub enum TypeDefinition {
    Scalar(ScalarType),
    Object(ObjectType),
    Interface(InterfaceType),
    Union(UnionType),
    Enum(EnumType),
    InputObject(InputObjectType),
}
Field

Represents a field within an object type:

#[derive(Clone, PartialEq)]
pub struct Field {
    name: FieldName,
    type_ref: TypeReference,
    arguments: Vec<Argument>,
    description: Option<String>,
    deprecation_reason: Option<String>,
}
Selection

Represents field selections in a query:

#[derive(Clone, PartialEq)]
pub enum Selection {
    Field {
        name: String,
        alias: Option<String>,
        arguments: Vec<(String, Value)>,
        selection_set: Vec<Selection>,
    },
    InlineFragment {
        type_condition: Option<String>,
        selection_set: Vec<Selection>,
    },
    FragmentSpread {
        name: String,
    },
}

Aggregates

SchemaAggregate

Manages the complete GraphQL schema with consistency boundaries:

pub struct SchemaAggregate {
    schema: Schema,
    resolver_registry: ResolverRegistry,
    validation_rules: Vec<ValidationRule>,
}

impl SchemaAggregate {
    pub fn validate_query(&self, query: &Query) -> ValidationResult {
        // Validate query against schema
    }
    
    pub fn execute_query(&self, query: Query) -> ExecutionResult {
        // Execute validated query
    }
}

Domain Services

SchemaValidator

Validates schema definitions for correctness:

pub struct SchemaValidator;

impl SchemaValidator {
    pub fn validate(&self, schema: &Schema) -> SchemaValidationResult {
        // Validate schema structure and rules
    }
}
QueryExecutor

Orchestrates query execution:

pub struct QueryExecutor {
    schema: Arc<Schema>,
    resolver_registry: Arc<ResolverRegistry>,
}

impl QueryExecutor {
    pub async fn execute(&self, query: Query) -> ExecutionResult {
        // Execute query with proper error handling
    }
}
QueryValidator

Validates queries against schema:

pub struct QueryValidator {
    schema: Arc<Schema>,
    rules: Vec<ValidationRule>,
}

impl QueryValidator {
    pub fn validate(&self, query: &Query) -> ValidationResult {
        // Apply all validation rules
    }
}

Repositories

SchemaRepository

Manages schema persistence and retrieval:

pub trait SchemaRepository {
    async fn save(&self, schema: Schema) -> Result<(), SchemaError>;
    async fn find_by_id(&self, id: SchemaId) -> Result<Option<Schema>, SchemaError>;
    async fn find_latest(&self) -> Result<Option<Schema>, SchemaError>;
}

Supporting Bounded Contexts

Resolver Context

Manages field resolution and data fetching:

pub struct ResolverContext {
    pub data_loaders: HashMap<String, Box<dyn DataLoader>>,
    pub user_context: Option<UserContext>,
    pub request_context: RequestContext,
}

Error Context

Handles error reporting and formatting:

pub struct GraphQLError {
    message: String,
    locations: Vec<SourceLocation>,
    path: Option<Vec<PathSegment>>,
    extensions: Option<ErrorExtensions>,
}

Architecture Layers

Domain Layer (src/domain)

domain/
β”œβ”€β”€ entities/
β”‚   β”œβ”€β”€ schema.rs
β”‚   β”œβ”€β”€ query.rs
β”‚   └── mod.rs
β”œβ”€β”€ value_objects/
β”‚   β”œβ”€β”€ types.rs
β”‚   β”œβ”€β”€ selections.rs
β”‚   └── mod.rs
β”œβ”€β”€ services/
β”‚   β”œβ”€β”€ validator.rs
β”‚   β”œβ”€β”€ executor.rs
β”‚   └── mod.rs
└── repositories/
    β”œβ”€β”€ schema_repository.rs
    └── mod.rs

Application Layer (src/application)

application/
β”œβ”€β”€ use_cases/
β”‚   β”œβ”€β”€ execute_query.rs
β”‚   β”œβ”€β”€ validate_schema.rs
β”‚   └── mod.rs
β”œβ”€β”€ services/
β”‚   β”œβ”€β”€ schema_service.rs
β”‚   β”œβ”€β”€ query_service.rs
β”‚   └── mod.rs
└── dto/
    β”œβ”€β”€ query_request.rs
    β”œβ”€β”€ execution_result.rs
    └── mod.rs

Infrastructure Layer (src/infrastructure)

infrastructure/
β”œβ”€β”€ http/
β”‚   β”œβ”€β”€ handlers/
β”‚   β”œβ”€β”€ middleware/
β”‚   └── mod.rs
β”œβ”€β”€ persistence/
β”‚   β”œβ”€β”€ memory/
β”‚   β”œβ”€β”€ redis/
β”‚   └── mod.rs
└── resolvers/
    β”œβ”€β”€ user_resolver.rs
    β”œβ”€β”€ post_resolver.rs
    └── mod.rs

Presentation Layer (src/presentation)

presentation/
β”œβ”€β”€ graphql/
β”‚   β”œβ”€β”€ schema.rs
β”‚   β”œβ”€β”€ resolvers.rs
β”‚   └── mod.rs
β”œβ”€β”€ rest/
β”‚   β”œβ”€β”€ health.rs
β”‚   └── mod.rs
└── websocket/
    β”œβ”€β”€ subscriptions.rs
    └── mod.rs

Domain Events

GraphQL operations can trigger domain events for cross-cutting concerns:

pub enum GraphQLEvent {
    QueryExecuted {
        query_id: QueryId,
        execution_time: Duration,
        field_count: usize,
    },
    SchemaUpdated {
        schema_id: SchemaId,
        version: SchemaVersion,
    },
    ValidationFailed {
        query_id: QueryId,
        errors: Vec<ValidationError>,
    },
}

Design Patterns

Repository Pattern

Abstract data access behind domain interfaces.

Command Query Separation

Separate read (Query) and write (Mutation) operations.

Specification Pattern

Encapsulate complex validation rules.

Observer Pattern

Handle domain events and side effects.

This domain model provides a solid foundation for implementing GraphQL server features while maintaining clean separation of concerns and domain-focused design.