Development Guide
View SourceThis guide covers setting up a development environment for contributing to Macula.
Prerequisites
- Erlang/OTP 26+ - Installation Guide
- Rebar3 - Erlang build tool (Installation)
- Git - Version control
- Docker (optional) - For multi-node testing
Quick Setup
# Clone the repository
git clone https://github.com/macula-io/macula.git
cd macula
# Fetch dependencies
rebar3 get-deps
# Compile
rebar3 compile
# Run tests
rebar3 eunit
# Start a shell with Macula loaded
rebar3 shell
Project Structure
Macula is organized as a single Erlang/OTP library with ~68 modules:
macula/
├── src/ # Source code (~68 .erl files)
│ ├── macula_quic*.erl # QUIC transport layer
│ ├── macula_protocol*.erl # Wire protocol encoding/decoding
│ ├── macula_connection*.erl # Connection management
│ ├── macula_gateway*.erl # Gateway components
│ ├── macula_routing*.erl # Kademlia DHT routing
│ ├── macula_pubsub*.erl # Pub/Sub messaging
│ ├── macula_rpc*.erl # RPC operations
│ └── macula_*.erl # Core utilities
├── test/ # EUnit tests
├── include/ # Header files (.hrl)
├── architecture/ # Architecture documentation
├── docs/ # User-facing documentation
├── examples/ # Example applications
└── rebar.config # Build configurationSee Project Structure for complete module documentation.
Running Tests
All Tests
rebar3 eunit
Specific Module Tests
rebar3 eunit --module=macula_id_tests
rebar3 eunit --module=macula_gateway_client_manager_tests
Test Coverage
rebar3 do eunit, cover
Multi-Node Integration Tests
cd docker
docker compose -f docker-compose.multi-node-test.yml build --no-cache
docker compose -f docker-compose.multi-node-test.yml up
Code Quality Standards
Macula follows Idiomatic Erlang principles:
Core Principles
- ✅ Pattern matching on function heads - Avoid
ifandcond - ✅ Guards instead of case - Use guards for simple conditions
- ✅ Shallow nesting - Keep nesting to 1-2 levels maximum
- ✅ Let it crash - Don't catch errors unless you can handle them meaningfully
- ✅ OTP behaviors - Use gen_server, gen_statem, supervisor where appropriate
Example: Good vs. Bad
❌ Bad:
process_message(Msg, State) ->
if
is_binary(Msg) ->
case decode_message(Msg) of
{ok, Data} ->
if
Data#data.type == request ->
handle_request(Data, State);
Data#data.type == response ->
handle_response(Data, State)
end
end
end.✅ Good:
%% Guard ensures binary input
process_message(Msg, State) when is_binary(Msg) ->
case decode_message(Msg) of
{ok, Data} -> handle_decoded_message(Data, State);
{error, Reason} -> {error, Reason}
end;
process_message(_Msg, _State) ->
{error, invalid_message}.
%% Pattern match on data type
handle_decoded_message(#data{type = request} = Data, State) ->
handle_request(Data, State);
handle_decoded_message(#data{type = response} = Data, State) ->
handle_response(Data, State).See CLAUDE.md (at repository root) for complete coding guidelines.
Building Documentation
Macula uses ex_doc for documentation generation:
rebar3 ex_doc
Generated docs appear in doc/ directory. Open doc/index.html in a browser.
Docker Development
Clean Build (Always After Code Changes)
# Prune cache and rebuild from scratch
docker builder prune -af
docker compose -f <compose-file> build --no-cache
Why? Docker build cache can use stale layers even after code changes. Always prune and rebuild when testing code changes.
Multi-Node Test Environment
cd docker
docker-compose -f docker-compose.multi-node-test.yml up
This starts:
- 1 registry node (gateway)
- 3 provider nodes (advertise services)
- 1 client node (discovers and calls services)
Memory Management
Macula implements comprehensive memory leak prevention. See Memory Management for details.
Key Mechanisms:
- Bounded connection pools (max 1,000 connections, LRU eviction)
- Client connection limits (max 10,000 clients, configurable)
- Service TTL/cleanup (5-minute TTL, 60-second cleanup interval)
- Stream cleanup on disconnect
- Caller process monitoring for RPC handlers
Monitoring:
%% Check connection pool size
macula_gateway_mesh:pool_size(GatewayPid).
%% Check client count
macula_gateway_client_manager:client_count(ManagerPid).
%% Check service registry size
macula_service_registry:service_count().Refactoring Status
Gateway Refactoring (COMPLETED - Jan 2025)
The gateway module has been refactored into 6 focused modules with comprehensive tests:
- ✅
macula_gateway_client_manager.erl- Client lifecycle (24 tests) - ✅
macula_gateway_pubsub.erl- Pub/Sub routing (31 tests) - ✅
macula_gateway_rpc.erl- RPC handler management (20 tests) - ✅
macula_gateway_mesh.erl- Mesh connection pooling (16 tests) - ✅
macula_gateway_dht.erl- DHT query forwarding (stateless) - ✅
macula_gateway_rpc_router.erl- Multi-hop RPC routing (17 tests) - ✅
macula_gateway_sup.erl- Supervision tree (24 tests)
Total: 132 tests, all passing.
Connection Refactoring (COMPLETED - Nov 2025)
The v0.7.0 nomenclature refactoring achieved separation of concerns:
macula_peer- High-level API facade for mesh operationsmacula_connection- Low-level QUIC transport layer
See CLAUDE.md for current architecture details.
Contributing Workflow
Read the documentation
- README.md (at repository root) - Project overview
- CLAUDE.md (at repository root) - Coding guidelines
Create a feature branch
git checkout -b feature/your-feature-nameWrite tests first (TDD approach preferred)
# Create test file touch test/macula_your_module_tests.erl # Write failing tests # Implement functionality # Verify tests pass rebar3 eunit --module=macula_your_module_testsFollow code quality standards
- Pattern matching over conditionals
- Guards instead of case where possible
- Maximum 1-2 levels of nesting
- Comprehensive tests for new functionality
Commit and push
git add . git commit -m "Add feature: your feature description" git push origin feature/your-feature-nameCreate pull request
- Describe what the PR does
- Reference any related issues
- Ensure all tests pass
- Follow PR template guidelines
Getting Help
- Issues: GitHub Issues
- Documentation: See
architecture/directory for detailed architecture docs