Development Guide

View Source

This guide covers setting up a development environment for contributing to Macula.

Prerequisites

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 configuration

See 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 if and cond
  • 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 operations
  • macula_connection - Low-level QUIC transport layer

See CLAUDE.md for current architecture details.

Contributing Workflow

  1. Read the documentation

    • README.md (at repository root) - Project overview
    • CLAUDE.md (at repository root) - Coding guidelines
  2. Create a feature branch

    git checkout -b feature/your-feature-name
    
  3. Write 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_tests
    
  4. Follow code quality standards

    • Pattern matching over conditionals
    • Guards instead of case where possible
    • Maximum 1-2 levels of nesting
    • Comprehensive tests for new functionality
  5. Commit and push

    git add .
    git commit -m "Add feature: your feature description"
    git push origin feature/your-feature-name
    
  6. Create 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

← Back to Documentation