Date: 2025-03-10
Status
Accepted
Context
Our book building system requires robust concurrency, fault tolerance, and maintainability. The previous Ruby implementation (ADR-0002) served well for initial development but has limitations in:
- Concurrent processing of multiple formats
- Process supervision and fault recovery
- Memory management for large books
- System scalability
- Type safety and pattern matching
Decision
We will implement the entire book building system in Elixir, leveraging:
1. OTP Architecture
# Application supervision tree
defmodule Codexis.Application do
use Application
def start(_type, _args) do
children = [
{Codexis.BuildManager, []},
{Codexis.Assets.Registry, []},
{Codexis.Assets.Cache, []},
{DynamicSupervisor, strategy: :one_for_one, name: Codexis.BuilderSupervisor},
{DynamicSupervisor, strategy: :one_for_one, name: Codexis.ValidatorSupervisor},
{Phoenix.PubSub, name: Codexis.PubSub}
]
opts = [strategy: :one_for_one, name: Codexis.Supervisor]
Supervisor.start_link(children, opts)
end
end
2. Concurrent Processing
defmodule Codexis.FormatGenerator do
def generate_formats(content, assets, metadata, options) do
options
|> get_builders()
|> Task.async_stream(fn {format, builder} ->
{format, builder.build(content, assets, metadata)}
end)
|> Enum.reduce(%{}, fn {:ok, {format, result}}, acc ->
Map.put(acc, format, result)
end)
end
end
3. Pattern Matching
defmodule Codexis.Builder do
@callback build(content :: Content.t(), options :: Keyword.t()) :: {:ok, Output.t()} | {:error, term()}
defmacro __using__(_opts) do
quote do
@behaviour Codexis.Builder
def build(content, options \\ []) do
with {:ok, validated} <- validate_content(content),
{:ok, processed} <- process_content(validated),
{:ok, output} <- generate_output(processed, options) do
{:ok, output}
end
end
end
end
end
4. Process Isolation
defmodule Codexis.BuildManager do
use GenServer
def build(content_path, options \\ []) do
GenServer.call(__MODULE__, {:build, content_path, options}, :infinity)
end
def handle_call({:build, path, options}, from, state) do
Task.start_link(fn ->
result = do_build(path, options)
GenServer.reply(from, result)
end)
{:noreply, state}
end
end
5. Error Handling
defmodule Codexis.ErrorHandler do
def handle_error({:error, :validation_failed} = error) do
Logger.error("Validation failed", error: error)
{:error, :validation_failed}
end
def handle_error({:error, :build_failed, details} = error) do
Logger.error("Build failed", error: error, details: details)
notify_admin(error)
{:error, :build_failed}
end
end
Consequences
Positive
- Concurrency: Efficient parallel processing of formats
- Fault Tolerance: OTP supervision ensures system stability
- Maintainability: Clear module structure and behaviors
- Type Safety: Pattern matching catches errors early
- Scalability: Ready for distributed processing
- Memory Management: Better handling of large books
- Testing: Property-based testing with ExUnit
Negative
- Learning Curve: Team needs to learn Elixir/OTP
- Ecosystem: Some Ruby gems need Elixir equivalents
- Migration Effort: Existing code needs porting
- Integration: External tools need Elixir wrappers
Migration Strategy
-
Phase 1: Core Components
- Port build pipeline
- Implement OTP supervision
- Create format builders
-
Phase 2: Supporting Systems
- Asset management
- Metadata handling
- Validation system
-
Phase 3: External Integration
- WikiJS publishing
- Tool wrappers
- CLI interface
-
Phase 4: Testing & Validation
- Port test suite
- Add property-based tests
- Performance validation
Compliance
Implementation must:
- Match or exceed Ruby version capabilities
- Maintain backward compatibility
- Follow Elixir best practices
- Include comprehensive tests
- Document all modules
Notes
- Monitor memory usage patterns
- Benchmark against Ruby version
- Document migration patterns
- Train team on Elixir/OTP
- Setup CI/CD for Elixir