Date: 2025-02-24
Status
Accepted
Context
The replatforming of our ebook conversion system from Ruby to Elixir represents a significant technical transition with several challenges:
-
Complex domain migration: We need to transfer complex domain logic while adapting to a fundamentally different programming paradigm (functional vs object-oriented).
-
Quality assurance needs: As outlined in our replatforming document, we aim to enhance build environment management, validation, and quality control.
-
Technical learning curve: The team needs to become proficient with Elixir/OTP concepts while maintaining productivity.
-
Risk management: Replatforming always carries the risk of regressions, lost functionality, or performance issues.
-
Iterative delivery requirements: We need to deliver value incrementally rather than through a "big bang" replatforming approach.
Our previous architectural decisions have established Domain-Driven Design (ADR-0011), Test-Driven Design (ADR-0012), and Behavior-Driven Design (ADR-0013) as core methodological approaches. However, we need an overarching engineering practice framework to tie these approaches together and guide our day-to-day development process.
Decision
We will adopt eXtreme Programming (XP) as our primary engineering practice framework for the replatforming effort, implementing the following core XP practices:
1. Pair Programming
- Elixir-Specific Implementation: Pairing experienced Elixir developers with domain experts to transfer knowledge in both directions
- Tools: Use Live Share in VS Code or Tmux for remote pairing
- Rotation: Rotate pairs daily to maximize knowledge sharing
- Specialized Pairing: Create dedicated "learning pairs" when introducing new Elixir concepts
# Example of code comment indicating pair programming session
# Pair: Jane (Elixir) & Tom (Domain)
# Date: 2025-03-01
defmodule Codexis.Workflow do
# Implementation with both domain and Elixir best practices
end
2. Test-First Development
- Integration with ADR-0012: Implement TDD as already defined
- Extension: Add specific practices for property-based testing of core conversion algorithms
- Emphasis: Focus on tests that document domain rules
# Example of test-first approach for a domain concept
defmodule Codexis.Metadata.AuthorTest do
use ExUnit.Case, async: true
test "format_author_name follows Chicago Manual of Style by default" do
# This test documents a domain rule about author name formatting
author = %Codexis.Metadata.Author{first: "Jane", last: "Smith"}
assert Codexis.Metadata.format_author_name(author) == "Smith, Jane"
end
end
3. Continuous Integration
- Automated Pipeline: Set up GitHub Actions workflow for continuous testing
- Fast Feedback: Implement test suite segmentation for quick feedback:
- Core domain tests (fast)
- Integration tests (medium)
- System tests (slow)
- Quality Gates: Enforce code quality metrics:
- Test coverage (minimum 85%)
- Code formatting (enforced by
mix format) - Static analysis with Credo
- Dialyzer type checking
# Example GitHub Actions workflow segment
name: Elixir CI
on:
push:
branches: [ main, development ]
pull_request:
branches: [ main, development ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: erlef/setup-beam@v1
with:
otp-version: '25'
elixir-version: '1.14'
- run: mix deps.get
- run: mix format --check-formatted
- run: mix credo --strict
- run: mix dialyzer
- run: mix test --exclude integration:true --exclude system:true
- run: mix test --only integration:true
- run: mix test --only system:true
4. Simple Design
- Simplicity Principles: Apply the four rules of simple design:
- Passes all tests
- Reveals intention
- No duplication
- Fewest elements
- Elixir-Specific Practices:
- Prefer pure functions
- Use pattern matching over conditionals
- Create small, focused modules
- Leverage Elixir's pipeline operator for readable transformations
# Example of simple design in Elixir
# BEFORE - Complex with conditionals
def process_format(format, content) do
if format == :pdf do
processed = prepare_for_pdf(content)
if processed != nil do
generate_pdf(processed)
else
{:error, "Failed to prepare for PDF"}
end
else
if format == :epub do
processed = prepare_for_epub(content)
if processed != nil do
generate_epub(processed)
else
{:error, "Failed to prepare for EPUB"}
end
else
{:error, "Unsupported format"}
end
end
end
# AFTER - Simple with pattern matching and pipelines
def process_format(:pdf, content) do
content
|> prepare_for_pdf()
|> case do
{:ok, prepared} -> generate_pdf(prepared)
{:error, reason} -> {:error, reason}
end
end
def process_format(:epub, content) do
content
|> prepare_for_epub()
|> case do
{:ok, prepared} -> generate_epub(prepared)
{:error, reason} -> {:error, reason}
end
end
def process_format(format, _content), do: {:error, "Unsupported format: #{format}"}
5. Refactoring
- Continuous Improvement: Establish regular refactoring sessions
- Refactoring Types:
- Technical debt reduction
- Performance improvements
- Improving domain alignment (with DDD)
- Enhancing testability
- Refactoring Safety: Ensure comprehensive tests before refactoring
# Example refactoring commit message format
"""
refactor: Improve format validation pipeline
- Extract reusable validation functions
- Simplify error handling with with-statements
- Add property-based tests for validation rules
- Improve naming to better reflect domain concepts
Performance impact: -15% memory usage, +5% throughput
Test coverage: 92% -> 94%
"""
6. Collective Code Ownership
- Practices:
- Regular code reviews
- Knowledge sharing sessions
- Documentation of non-obvious design decisions
- Consistent coding standards
- Tools:
- Pull request templates
- Standardized module documentation
- Architecture decision records (continuing our ADR practice)
defmodule Codexis.FormatConversion do
@moduledoc """
Handles conversion between different ebook formats.
## Design Notes
- Uses adapter pattern to abstract external tool interactions
- Implements circuit breaker for external tool failures
- See ADR-020 for details on tool integration approach
## Ownership
Primary: Team Alpha
Domain experts: Jane (PDF), Tom (EPUB)
"""
end
7. Small Releases
- Incremental Migration: Define bounded contexts that can be migrated independently
- Feature Flags: Use runtime configuration to enable/disable new Elixir implementations
- Parallel Operation: Support running Ruby and Elixir components side-by-side during transition
- Validation: Implement comparison testing between old and new implementations
defmodule Codexis.FeatureFlags do
def enabled?(feature) do
Application.get_env(:elib, :features, %{})[feature] == true
end
def use_new_implementation?(feature) do
enabled?("new_#{feature}_implementation")
end
end
# Usage example
def convert_to_pdf(manuscript) do
if Codexis.FeatureFlags.use_new_implementation?(:pdf_conversion) do
Codexis.Formats.PDF.convert(manuscript)
else
Codexis.Legacy.invoke_ruby_pdf_conversion(manuscript)
end
end
8. Sustainable Pace
- Work Planning: Schedule focused development time balanced with learning and exploration
- Knowledge Management: Document Elixir patterns and lessons learned
- Technical Practices:
- Regular code quality reviews
- Technical debt tracking and prioritization
- "Friday Focus" time for learning and exploration
# Example technical debt tracker in code
defmodule Codexis.TechnicalDebt do
@moduledoc """
Technical debt tracker.
## Known Issues
- [MEDIUM] Refactor PDF validation to use property-based tests (planned for Sprint 5)
- [HIGH] Format conversion needs better error handling (blocking feature X)
- [LOW] Config loading could be more efficient (optimize if becomes bottleneck)
"""
end
9. Whole Team
- Collaborative Practices:
- Daily stand-ups focused on the replatforming effort
- Weekly domain deep-dives with subject matter experts
- Bi-weekly retrospectives for process improvement
- Regular demos of new Elixir-based components
Implementation Plan
- Team Training: Conduct initial XP and Elixir training sessions
- Environment Setup: Configure development environment and CI pipeline
- Practice Introduction: Phase in XP practices over 3 sprints:
- Sprint 1: Pair Programming + TDD
- Sprint 2: Add Continuous Integration + Small Releases
- Sprint 3: Full XP implementation
- Cadence Establishment:
- Daily: Stand-ups and pair switching
- Weekly: Code reviews and knowledge sharing
- Bi-weekly: Retrospectives and process adjustment
Consequences
Positive
- Reduced migration risk: Continuous testing and small releases minimize replatforming risks
- Knowledge transfer acceleration: Pair programming accelerates Elixir knowledge acquisition
- Quality improvement: Test-first development supports the enhanced quality goals
- Team alignment: XP practices ensure the whole team understands both domain and technology
- Flexible adaptation: Regular retrospectives allow adapting practices to project needs
- Documentation improvement: Emphasis on collective code ownership encourages better documentation
- Improved technical design: Simple design principles lead to more maintainable Elixir code
- Faster feedback cycles: Continuous integration provides quick feedback on changes
Negative
- Initial productivity impact: Learning curve for both XP and Elixir may slow initial velocity
- Perceived overhead: Practices like pair programming may seem costly in the short term
- Tool configuration effort: Setting up the infrastructure for CI/CD requires initial investment
- Process adoption challenges: Team members used to other methodologies may resist change
- Pairing logistics: Remote or hybrid work arrangements complicate pair programming
Mitigation Strategies
- Gradual adoption: Phase in XP practices rather than implementing all at once
- ROI tracking: Measure and demonstrate benefits of XP practices to build team buy-in
- Infrastructure automation: Automate environment setup to reduce overhead
- Pairing tools: Invest in good tooling for remote pair programming
- Flexibility: Adapt XP practices to our specific context rather than strict dogmatic adherence
- Champions: Identify XP champions within the team to help drive adoption
- Experience sharing: Document and share success stories and lessons learned
References
- Kent Beck, "Extreme Programming Explained"
- Martin Fowler, "Refactoring: Improving the Design of Existing Code"
- José Valim, "Elixir in Action"
- Bruce Tate & James Edward Gray II, "Designing Elixir Systems with OTP"
- Robert C. Martin, "Clean Code"
- Eric Evans, "Domain-Driven Design" (for XP/DDD integration)
- Nat Pryce & Steve Freeman, "Growing Object-Oriented Software, Guided by Tests"