This presentation introduces Mailman, a Clojure event-driven library designed to simplify the development of maintainable and testable event-driven applications. Wout Duerinck, the creator, outlines Mailman's core design principles, emphasizing testability, flexibility, and independence from underlying message brokers. The library abstracts asynchronous event handling through a system of event posters, receivers, listeners, and a sophisticated routing mechanism that dispatches events to handlers. A key feature is the use of interceptors, inspired by Pedestal, for pre- and post-processing, and a unique approach where handler return values are interpreted as new events, streamlining event chaining. The presentation covers various broker implementations (JMS, Manifold, in-memory) and a robust testing methodology, including a replace-interceptors utility to mock side effects during router testing.
Mailman: An Event-Driven Library for Clojure
0:00 Introduction to Mailman: Wout Duerinck introduces Mailman, his event-driven Clojure library. He shares his background as a TDD advocate, founder of Monkey Projects, and developer of MonkeyCI, a Clojure-based CI/CD pipeline, which motivated Mailman's creation.
1:34 Presentation Agenda: The talk will cover Mailman's high-level view, design decisions, core concepts, functionality, testing strategies, various implementations, and conclude with a live coding session and Q&A.
2:43 Defining Event-Driven: Mailman is a library for applications where the flow is driven by asynchronous events, not synchronous requests. It was developed to address challenges in managing events maintainably and testably within event-heavy applications like CI/CD tools.
Event Poster: A protocol with a post-event function to send events to the underlying broker, supporting batching for efficiency.
Event Receiver: A protocol for obtaining events, either by explicit polling or by add-listenering.
Listener: An object that gets notified when an event is received, typically containing a handler function. It can also be unregistered.
Handler's Return Value (Key Takeaway): If a handler function returns a value, Mailman interprets this as new events to be sent out, reducing the need for explicit post-event calls within logic.
Router: Decides which handlers receive an event based on configured rules (e.g., event type). Created using make-router, which accepts a routing structure and optional configurations for a matcher, invoker, and global interceptors.
Interceptors: A powerful concept (inspired by Pedestal) for chaining functions that operate on a context map. They have enter and leave functions, allowing for pre/post-processing, side effects, logging, validation, and security checks.
Context: A mutable map passed through the interceptor chain, always containing the incoming event and potentially augmented by interceptors. It also stores the result (return value) from the handler.
Broker: A combination of event poster and event receiver implementations, acting as a thin layer over the actual messaging system.
JMS: For Java-based messaging systems (e.g., ArtemisMQ, ActiveMQ), using the speaker's monkey-jms library.
Manifold: An in-memory asynchronous library for intra-process eventing.
In-Memory: Simple Java Queue-based implementation, primarily for testing purposes.
Future (Kafka): Kafka is a strong candidate for a future implementation.
28:15 Event Flow Overview: An event flows from the broker to listeners, then to the router which uses a matcher to find relevant handler(s). Each handler is an interceptor chain that processes the event within a context. Any result from the handler is then posted as new events back to the broker.
Unit Tests: For individual handlers and interceptors (treating them as regular functions).
Router Tests (Most Important): Mailman provides replace-interceptors to swap out expensive or side-effecting interceptors with mocks or fakes, enabling fast and isolated testing of routing logic.
Broker Tests: Integration or end-to-end tests, often using the in-memory broker, primarily for serialization/deserialization.
Demonstrates a basic router handling an event, showing the context and the handler's nil return.
Shows an interceptor adding a timestamp to the context and a handler returning a new event.
Illustrates how the in-memory broker processes the initial event and then automatically dispatches the new event returned by the first handler.
Highlights the replace-interceptors function to replace a "slow" interceptor with a "fake" one during testing, ensuring fast and predictable tests.
Demonstrates connecting to an external JMS broker (Artemis), using a destination mapper, adding a listener to an "in" topic, posting an event, showing the event's processing, and the subsequent "out" event being handled by a second listener.
Shows how to unregister a listener, stopping event processing for that destination.
53:01 Conclusion: Wout Duerinck thanks the audience and provides links to Mailman's GitHub repository and MonkeyCI.