Date: 2025-02-24
Status
Accepted
Context
Our project to replatform the ebook conversion system from Ruby to Elixir involves complex domain logic, multiple stakeholders, and intricate workflows. As documented in our replatforming strategy, we need to ensure:
- Complete preservation of existing functionality during migration
- Proper leverage of Elixir/OTP advantages (concurrency, fault tolerance)
- Enhanced build environment management and validation
- Improved quality control and comprehensive auditing
- Support for flexible workflow automation
The following factors make communication and shared understanding particularly important:
- Cross-functional team involvement: Content experts, developers, operations, and quality assurance all have different perspectives on system behavior
- Complex business rules: Ebook conversion involves intricate format-specific rules and validations
- External stakeholder expectations: Authors and publishers have specific quality expectations for final output
- Transitioning domain knowledge: Moving from Ruby to Elixir requires preserving domain expertise while adapting to new paradigms
- Integration complexity: The system interacts with multiple external tools (LaTeX, Calibre, etc.) with their own behaviors
While Test-Driven Design (as documented in ADR-0012) provides technical quality assurance, we also need an approach that bridges technical implementation with domain understanding and stakeholder expectations.
Decision
We will adopt Behavior-Driven Design (BDD) as a complementary approach to our Test-Driven Design strategy. BDD will serve as a methodology to:
- Define system behavior in business terms: Document the expected behavior of the system using a language that all stakeholders understand
- Drive development from scenarios: Use concrete examples and scenarios to guide implementation
- Bridge communication gaps: Provide a common language between technical and non-technical team members
- Validate business requirements: Ensure the system fulfills actual business needs
Implementation Strategy
We will implement BDD using the following approach:
1. Behavior Specification
We will use Gherkin syntax to define behavior specifications in a human-readable, executable format:
Feature: Generating multiple ebook formats
As a publisher
I want to generate multiple ebook formats from a single manuscript
So that I can distribute content across different platforms
Scenario: Converting a LaTeX manuscript to PDF and EPUB
Given a valid LaTeX manuscript with frontmatter
And all required dependencies are installed
When I initiate the build process with PDF and EPUB formats selected
Then the system should generate a valid PDF file
And the system should generate a valid EPUB file
And both formats should have consistent styling
And the build log should report successful completion
2. Tools and Framework
We will implement BDD using:
- Cabbage: For Gherkin support in Elixir
- WhiteBread: As an alternative for BDD in Elixir projects
- Custom step definitions: To connect Gherkin steps with Elixir code
- Integration with ExUnit: To maintain compatibility with our test infrastructure
3. BDD Workflow
-
Collaborative specification writing:
- Involve domain experts, developers, and QA in writing feature files
- Use specification workshops to identify and clarify behaviors
- Document edge cases and exception flows
-
Implementation:
- Write step definitions that connect specifications to code
- Integrate with existing TDD practices (use BDD for higher-level behaviors, TDD for implementation details)
- Automate scenario execution as part of CI/CD pipeline
-
Continuous validation:
- Use scenarios as living documentation
- Update specifications as requirements evolve
- Report scenario execution in business-friendly terms
4. Example Implementation
Example step definition in Elixir:
defmodule Codexis.BDD.FormatGenerationSteps do
use Cabbage.Feature, async: true, file: "features/format_generation.feature"
alias Codexis.Manuscripts
alias Codexis.BuildManager
# Step definitions
given_ ~r/^a valid LaTeX manuscript with frontmatter$/, _vars, state do
manuscript = Manuscripts.sample_manuscript(:latex_with_frontmatter)
{:ok, Map.put(state, :manuscript, manuscript)}
end
and_ ~r/^all required dependencies are installed$/, _vars, state do
{:ok, env} = BuildManager.verify_environment()
{:ok, Map.put(state, :environment, env)}
end
when_ ~r/^I initiate the build process with PDF and EPUB formats selected$/, _vars, state do
formats = [:pdf, :epub]
{:ok, build_id} = Codexis.BuildManager.start_build(state.manuscript, formats)
{:ok, Map.put(state, :build_id, build_id)}
end
then_ ~r/^the system should generate a valid PDF file$/, _vars, state do
assert {:ok, pdf_path} = Codexis.BuildManager.get_artifact(state.build_id, :pdf)
assert Codexis.Validation.validate_pdf(pdf_path) == :ok
{:ok, state}
end
and_ ~r/^the system should generate a valid EPUB file$/, _vars, state do
assert {:ok, epub_path} = Codexis.BuildManager.get_artifact(state.build_id, :epub)
assert Codexis.Validation.validate_epub(epub_path) == :ok
{:ok, state}
end
# Additional step definitions...
end
5. Organizational Structure
We will organize our BDD artifacts as follows:
elib/
features/
build_management.feature
format_generation.feature
validation.feature
workflow_automation.feature
test/
bdd/
steps/
build_management_steps.ex
format_generation_steps.ex
validation_steps.ex
workflow_automation_steps.ex
6. Integration with Domain-Driven Design
Our BDD approach will reinforce our Domain-Driven Design (ADR-0011) by:
- Using ubiquitous language in feature descriptions
- Organizing features around bounded contexts
- Validating domain model correctness through business scenarios
- Documenting domain rules in executable specifications
Consequences
Positive
- Improved communication: Common language between technical and business stakeholders
- Clear requirements: Explicit, executable specifications reduce misunderstandings
- Focus on value: Development prioritizes features with clear business value
- Living documentation: Specifications serve as always-current documentation
- Earlier bug detection: Business logic errors caught at specification level
- Easier onboarding: New team members can understand system behavior from specifications
- Validation of DDD model: BDD scenarios help verify that domain model captures business needs
- Smoother migration: Clear behavior specifications ensure functional equivalence after replatforming
Negative
- Learning curve: Team must learn BDD concepts and Gherkin syntax
- Maintenance overhead: Feature files must be kept in sync with implementation
- Potential verbosity: Poor implementation can lead to repetitive step definitions
- Tool immaturity: Elixir BDD tools are less mature than in other ecosystems
- Process adaptation: Team must adapt development process to include specification writing
- Over-specification risk: Excessive detail in scenarios can make them brittle
Mitigation Strategies
- BDD training: Provide team training focused on effective specification writing
- Start selective implementation: Begin with high-value, complex features rather than trying to cover everything
- Develop reusable steps: Create a library of common steps to reduce repetition
- Regular refinement: Schedule regular sessions to review and refine specifications
- Custom tooling: Develop helpers to address gaps in Elixir BDD tooling
- Integration with CI/CD: Automate scenario execution and reporting
- Specification reviews: Include domain experts in reviewing specifications before implementation
- Balance with TDD: Use BDD for business-facing tests and TDD for developer-facing tests
References
- Dan North, "Introducing BDD"
- Gáspár Nagy & Seb Rose, "BDD Books: Discovery"
- Matt Wynne & Aslak Hellesøy, "The Cucumber Book"
- Liz Keogh, "Behavior-Driven Development"
- Elixir Cabbage documentation
- WhiteBread Elixir documentation
- Eric Evans, "Domain-Driven Design" (for DDD/BDD integration)