BUCKYVERSE

Date: 2025-02-24

Status

Accepted

Context

As we migrate our existing Ruby-based ebook conversion system to Elixir, we need to establish a clear architectural approach that will:

  1. Accurately capture the complex domain logic of ebook creation, conversion, and validation
  2. Enable us to leverage Elixir/OTP's concurrency, fault tolerance, and distribution capabilities
  3. Support the multiple format generation pipeline with improved build management
  4. Facilitate clear boundaries between different system components
  5. Provide a model that can evolve as the domain requirements change

Our ebook conversion system encompasses complex workflows with various specialized tools (LaTeX, Calibre, etc.), quality validation, and build automation. The domain complexity extends beyond simple CRUD operations and involves intricate business rules around format conversion, metadata management, and quality control.

Decision

We will adopt Domain-Driven Design (DDD) principles in our Elixir replatforming effort, specifically:

  1. Bounded Contexts: We will define clear bounded contexts for:

    • Build Environment Management
    • Workflow Automation
    • Content Processing
    • Format Generation
    • Quality Control & Validation
    • Metadata Management
    • Audit System
  2. Ubiquitous Language: We will establish a consistent vocabulary within each bounded context that reflects the domain concepts. This language will be used in code, documentation, and team communications.

  3. Aggregates and Entities:

    • Define key aggregates such as Manuscript, Build, Workflow, and ValidationReport
    • Identify entities with identity such as Metadata, FormatOutput, and AuditLog
    • Use value objects for immutable concepts like ValidationRule, FormatSpecification, and BuildEnvironment
  4. Domain Events: Leverage Elixir's process model and pub/sub capabilities to implement domain events for:

    • Build state transitions
    • Format generation completion
    • Validation failures/successes
    • Configuration changes
  5. Repositories: Implement context-specific repositories to provide domain-focused data access

  6. Services: Create domain services for operations that don't naturally belong to entities:

    • FormatConversionService
    • ValidationService
    • BuildOptimizationService
  7. Anti-corruption Layers: Create adapters for external tools (LaTeX, Calibre) to translate between their models and our domain model

Implementation Strategy

In Elixir, we'll implement DDD using:

  1. Module structure that reflects bounded contexts:

    lib/
      elib/
        build_manager/
        workflow/
        validation/
        metadata/
        format/
        audit/
    
  2. Structs and protocols for domain entities and value objects:

    defmodule Codexis.Manuscript do
      @enforce_keys [:id, :content, :metadata]
      defstruct [:id, :content, :metadata, :status, artifacts: %{}]
    end
    
  3. GenServers and Supervisors for managing entity lifecycles and implementing domain services:

    defmodule Codexis.BuildManager do
      use GenServer
      # Domain logic for build management
    end
    
  4. Registry and PubSub for implementing domain events:

    defmodule Codexis.DomainEvents do
      def publish(event) do
        Phoenix.PubSub.broadcast(Codexis.PubSub, "domain_events", {:event, event})
      end
    end
    
  5. Ecto schemas for persistence where appropriate, keeping domain logic separate:

    defmodule Codexis.Metadata.Schema do
      use Ecto.Schema
      # Schema definitions
    end
    

Consequences

Positive

  1. Improved domain understanding: DDD will help the team develop a shared understanding of the complex ebook domain
  2. More maintainable code: Clear boundaries between contexts will make the system more modular
  3. Better alignment with Elixir philosophy: The actor model of Elixir naturally supports modeling domain entities as processes
  4. Enhanced testability: Domain-centric modules can be tested in isolation
  5. Future extensibility: New formats or workflow steps can be added with minimal changes to existing components
  6. Parallel development: Different bounded contexts can be developed by different team members concurrently

Negative

  1. Learning curve: Team members need to learn both Elixir and DDD principles
  2. Initial development overhead: Establishing the DDD structure requires more upfront design
  3. Potential over-engineering risk: Need to balance DDD purity with practical implementation
  4. Integration complexity: Managing interactions between bounded contexts adds complexity

Mitigation Strategies

  1. Schedule knowledge-sharing sessions on DDD and Elixir
  2. Start with a minimal viable DDD implementation and refine iteratively
  3. Regular architecture reviews to prevent overengineering
  4. Create clear interface contracts between bounded contexts
  5. Document the ubiquitous language in a project glossary
  6. Implement metrics to track performance impacts of the design

References