← Back to all projects

LEARN CAMEL FROM SCRATCH IN C

Learn Apache Camel: By Building a Mini-Engine in C

Goal: Deeply understand the core concepts of Enterprise Integration Patterns (EIP) and the architecture of a routing engine like Apache Camel by building your own simplified version, “C-Mule,” from scratch in C.


Why Build a Camel Clone in C?

Apache Camel is a powerful integration framework based on the book Enterprise Integration Patterns. It provides a high-level Domain Specific Language (DSL) to route, transform, and mediate messages between different systems. While using Camel is powerful, the DSL can feel like “magic.”

Building a simplified version in C will strip away all the magic and force you to understand:

  • The Core Mechanics: How does a message (Exchange) flow through a route? How are components loaded and endpoints resolved?
  • Data Structures: What data structures are needed to represent routes, messages, headers, and the runtime context?
  • System-Level Thinking: How do you handle I/O for files, standard input, or network sockets? How do you manage memory and resources?
  • The “Why” of EIPs: Why do patterns like Content-Based Routers or Splitters exist? You will know because you will have to implement their logic yourself.

After completing this project, you will not only understand Apache Camel, but you will have a deep, practical knowledge of software architecture, system design, and the fundamental patterns that govern data flow in any complex application.


Core Concept Analysis: The Architecture of an Integration Engine

At its heart, Apache Camel is a machine for executing pipelines, which it calls Routes.

  // A simple Camel route in its Java DSL
  from("file:inbox?delete=true")
    .filter(header("type").isEqualTo("order"))
    .to("jms:queue:orders");

Let’s break this down into the core components you will build in C:

┌─────────────────────────────────────────────────────────────────────────┐
│                           The "C-Mule" Context                          │
│          (A master struct holding all resources and routes)             │
│                                                                         │
│   ┌───────────────────────────────────────────────────────────────────┐ │
│   │                              A Route                              │ │
│   │                                                                   │ │
│   │   [ Consumer ]───────►[ Processor ]───────►[ Producer ]            │ │
│   │ (from endpoint)      (e.g., a filter)     (to endpoint)           │ │
│   └───────────────────────────────────────────────────────────────────┘ │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
       ▲                      │                      ▲
       │                      ▼                      │
┌──────────────┐   ┌────────────────────┐   ┌────────────────┐
│ An Endpoint  │   │ An Exchange        │   │ A Component    │
│ (e.g., file:in)│   │ (The Message Packet) │   │ (The Endpoint Factory) │
│              │   │                    │   │                │
│ • Creates a  │   │ • Headers (map)    │   │ • Manages      │
│   Consumer   │   │ • Body (void*)     │   │   endpoints of │
│ • Consumes data│   │ • Travels the route│   │   a certain type│
└──────────────┘   └────────────────────┘   └────────────────┘

Key Components You Will Build in C

  1. The Context (cmule_context): A master struct that initializes and tears down the entire engine. It will hold a list of active routes and loaded components.
  2. The Exchange (cmule_exchange): A struct representing the message in flight. It must contain:
    • A headers map (which you might implement as a linked list of key-value pairs).
    • A body (likely a void* pointer with a separate size_t for length) to hold any kind of data.
  3. The Route (cmule_route): A struct that contains a starting Consumer and a linked list of Processors.
  4. Processors (cmule_processor): A struct containing a function pointer: void (*process)(cmule_exchange* exchange);. These are the steps in your pipeline (filters, transformers, etc.).
  5. Endpoints (cmule_endpoint): A struct representing a source or destination of data. It’s a factory for Producers and Consumers.
  6. Consumer (cmule_consumer): The start of a route. It’s responsible for creating Exchange objects from an external source (like a file or a socket) and pushing them into the route’s processor chain.
  7. Producer (cmule_producer): The end of a processing step. It’s responsible for sending the Exchange’s data to an external system. The to(...) part of a route creates a producer.
  8. Component (cmule_component): The factory for Endpoints. You’ll have a file_component, a stdio_component, etc. The context will manage a registry of these components.

The Project: Build “C-Mule”, a Mini-Integration Engine

This is a single, large project broken down into smaller, buildable stages. Each stage implements a core piece of the final engine.

Main Programming Language: C (C99 or later) Key External Libraries: None to start, pthreads for later stages. Main Book: “Enterprise Integration Patterns” by Gregor Hohpe and Bobby Woolf. This is your bible. For each EIP you implement, you should read the corresponding chapter in this book first.


Stage 1: The Core Data Structures

  • File: LEARN_CAMEL_FROM_SCRATCH_IN_C.md
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: Data Structures / C Programming
  • Goal: Define the fundamental structs for the engine.

What you’ll build: Create the header files (cmule.h, exchange.h, etc.) that define cmule_context, cmule_exchange, cmule_message, and a simple key-value store for message headers.

Core challenges you’ll face:

  • Designing the structs → maps to thinking about what data is needed at each level
  • Creating a simple Map/Dictionary → maps to implementing a linked list or hash table for headers
  • Handling arbitrary body data → maps to using void* pointers and length fields for flexibility
  • Memory Management → maps to writing create and destroy functions for each struct to prevent leaks

Key Concepts:

  • Structs and Pointers: “The C Programming Language” (K&R) - Ch 5 & 6
  • Manual Memory Management: malloc, free, and disciplined resource handling.

Real world outcome: A set of header files and C files that can create and destroy a blank cmule_exchange object with headers and a body, all without memory leaks (verified with Valgrind).

Implementation Hints:

// exchange.h
typedef struct {
    char* key;
    char* value;
    struct HeaderNode* next;
} HeaderNode;

typedef struct {
    HeaderNode* headers;
    void* body;
    size_t body_len;
} cmule_message;

typedef struct {
    cmule_message* in_message;
    cmule_message* out_message; // Optional, for certain patterns
} cmule_exchange;

cmule_exchange* create_exchange();
void destroy_exchange(cmule_exchange* ex);
void set_header(cmule_exchange* ex, const char* key, const char* value);
const char* get_header(cmule_exchange* ex, const char* key);
void set_body(cmule_exchange* ex, void* body, size_t len);

Stage 2: The Processor Chain and a stdio Component

  • File: LEARN_CAMEL_FROM_SCRATCH_IN_C.md
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: Function Pointers / System Architecture
  • Goal: Create the route execution logic and the simplest possible I/O.

What you’ll build: The cmule_route struct, which will contain a linked list of cmule_processor function pointers. You will also build your first component: stdio, which can read from stdin and write to stdout.

Core challenges you’ll face:

  • Using function pointers → maps to the core of the Processor pattern
  • Implementing a component registry → maps to a simple mechanism in the cmule_context to find and load components by name
  • Building a simple Consumer → maps to the stdio:in consumer will read a line from stdin, create an Exchange, and start it on a route
  • Building a simple Producer → maps to the stdio:out producer will take an Exchange and print its body to stdout

Key Concepts:

  • Function Pointers in C: A core C concept for callbacks and plugins.
  • Plugin Architecture: Your component registry is a basic plugin system.

Real world outcome: A running C program that defines a simple route in code. When you run it, you can type a message, press Enter, and see that message printed back out to the console, proving the route executed.

Implementation Hints:

// The route execution logic
void execute_route(cmule_route* route, cmule_exchange* ex) {
    // Loop through the processor list
    for (ProcessorNode* p = route->processors; p != NULL; p = p->next) {
        p->processor_func(ex); // Call the function pointer
    }
}

// A simple processor function
void to_uppercase_processor(cmule_exchange* ex) {
    char* body = (char*)ex->in_message->body;
    // Loop through body and convert to uppercase...
}

// main.c
// 1. Create context
// 2. Create route
// 3. Add a producer processor for "stdio:out" to the end of the route
// 4. Create a consumer for "stdio:in"
// 5. The consumer reads from stdin, creates an exchange, and calls execute_route().

Stage 3: The File Component

  • File: LEARN_CAMEL_FROM_SCRATCH_IN_C.md
  • Difficulty: Level 3: Advanced
  • Knowledge Area: File I/O / Polling
  • Goal: Interact with the filesystem, a core integration task.

What you’ll build: A file component. The file:inbox consumer will poll a directory, and for each file it finds, it will process it as a message and then delete it. The file:outbox producer will write the message body to a new file in a directory.

Core challenges you’ll face:

  • Polling a directory → maps to using dirent.h to scan for files
  • Reading and writing files → maps to standard C file I/O (fopen, fread, fwrite)
  • Resource management → maps to ensuring files are closed and memory is freed even if errors occur
  • Parsing endpoint parameters → maps to handling options like file:inbox?delete=true

Key Concepts:

  • File I/O in C: stdio.h and related libraries.
  • Directory Traversal: POSIX dirent.h.
  • Polling Loop: A while(true) loop with a sleep() is a simple but effective starting point.

Real world outcome: You can run your program, drop a text file into an inbox directory, and a few seconds later, see it disappear and a new file with the (possibly transformed) content appear in an outbox directory. You’ve built a basic file-mover.


Stage 4: The Content-Based Router EIP

  • File: LEARN_CAMEL_FROM_SCRATCH_IN_C.md
  • Difficulty: Level 3: Advanced
  • Knowledge Area: EIPs / Logic / Composition
  • Goal: Implement your first real integration pattern.

What you’ll build: A special choice processor. This processor will not modify the message itself but will conditionally route it down different sub-pipelines based on its content or headers.

Why it teaches Camel: This is the if/then/else of integration. It demonstrates how a “pattern” is just a more complex processor composed of other processors, and it’s fundamental to any real-world routing logic.

Core challenges you’ll face:

  • Designing the choice processor struct → maps to it needs to hold multiple “when” clauses, each with a predicate function and a sub-chain of processors
  • Predicate functions → maps to using more function pointers: bool (*predicate)(cmule_exchange*)
  • Composition → maps to the choice processor will call the same execute_route logic on its sub-chains

Key Concepts:

  • EIP Book: Chapter on “Content-Based Router”.
  • Composition over Inheritance: You are building complex behavior by composing simple pieces.

Real world outcome: You can create a route that reads files from an inbox, and if a file’s name contains “REPORT”, it gets written to the reports directory; otherwise, it goes to the other directory.

Implementation Hints:

// Pseudo-code for the choice processor function
void choice_processor(cmule_exchange* ex) {
    // Cast the processor's data to your choice_processor_data struct
    choice_data* data = (choice_data*) current_processor->data;

    for (when_clause* w = data->when_clauses; w != NULL; w = w->next) {
        if (w->predicate_func(ex)) { // If the predicate matches...
            // Execute the sub-chain for this when clause
            execute_processor_chain(w->processors, ex);
            return; // Stop processing this choice block
        }
    }

    if (data->otherwise_processors != NULL) {
        // Execute the "otherwise" chain
        execute_processor_chain(data->otherwise_processors, ex);
    }
}

Stage 5: The Splitter EIP

  • File: LEARN_CAMEL_FROM_SCRATCH_IN_C.md
  • Difficulty: Level 4: Expert
  • Knowledge Area: EIPs / Memory Management / Flow Control
  • Goal: Handle messages that contain multiple parts.

What you’ll build: A split processor. It will take a single Exchange, split its body based on a delimiter (e.g., a newline), create a copy of the exchange for each part, and send each copy down the rest of the pipeline.

Why it teaches Camel: This is a very common and powerful pattern. It teaches you about message creation, lifecycle, and the concept of a single incoming message resulting in multiple outgoing messages. It will test your memory management skills.

Core challenges you’ll face:

  • Splitting the body → maps to string manipulation (strtok_r) or custom logic for binary data
  • Cloning exchanges → maps to writing a deep_copy_exchange function that correctly copies headers and the new, smaller body part
  • Executing the rest of the pipeline → maps to passing each new exchange to the *next processor in the main route*

Key Concepts:

  • EIP Book: Chapter on “Splitter”.
  • Deep vs. Shallow Copy: You must be careful to copy data, not just pointers, to ensure each split message is independent.

Real world outcome: You can drop a single CSV file with 10 lines into an inbox, and your engine will create 10 separate files in an outbox, each containing one line from the original CSV.


Stage 6: The TCP/IP Component

  • File: LEARN_CAMEL_FROM_SCRATCH_IN_C.md
  • Difficulty: Level 4: Expert
  • Knowledge Area: Network Programming / Concurrency
  • Goal: Make your engine network-aware.

What you’ll build: A tcp component. The tcp:server:9090 consumer will listen for incoming TCP connections. For each message received, it will start a new Exchange on a route. The tcp:client:localhost:9090 producer will send an Exchange’s body over a TCP socket.

Core challenges you’ll face:

  • Socket Programming in C → maps to socket(), bind(), listen(), accept(), read(), write()
  • Concurrency → maps to using pthreads (or fork) so your accept() loop doesn’t block the rest of the engine. Each client connection should be handled in its own thread.
  • Defining a wire protocol → maps to how do you know when a message ends? You’ll need a simple protocol, like prefixing each message with its length.

Key Concepts:

  • TCP/IP Sockets: “Beej’s Guide to Network Programming” is a classic resource.
  • Concurrency: POSIX Threads (pthreads) tutorial.

Real world outcome: You can run your C-Mule engine with a TCP server route. Then, from another terminal, you can use netcat to send a message (e.g., echo "hello world" | nc localhost 9090). Your engine will receive the message and route it, for example, to a file in an outbox directory.


Summary of Stages

Stage Goal Key C Concept EIP Implemented
1. Core Structs Define the data model struct, malloc/free N/A
2. Processor Chain Build the execution engine Function Pointers Pipes and Filters
3. File Component Interact with filesystem File I/O (stdio.h) N/A
4. Content-Based Router Add conditional logic Composition Content-Based Router
5. Splitter Process composite messages Deep Copying Splitter
6. TCP Component Go over the network Sockets, pthreads N/A

This project is challenging but will give you an unparalleled understanding of the problems that frameworks like Apache Camel are designed to solve. Good luck!