ClojureTV
Enterprise Clojure systems scale reliably only when teams eliminate ambient global state through lifecycle components and strictly audit third-party network libraries to prevent thread exhaustion.
AI-assisted summary of Anthony Marcar - Clojure At Scale @WalmartLabs — original source. Check the original for full context.
Source published:
Key ideas
- WalmartLabs rebuilt their core eReceipts platform in Clojure across 20 microservices, 70 Leiningen projects, and 50,000 lines of code, sustained by a core team of only eight engineers. (2:00)
- The team eradicated ambient top-level state by adopting Stuart Sierra's Component library, allowing multi-instance database connections, deterministic lifecycle ordering, and isolated lexical passing. (4:52)
- To bypass the Maven jar barrier in multi-project monorepos, WalmartLabs deployed an aggregation script that consolidates 70 distinct project.clj files into a synthetic root project for unified REPL workflows. (27:01)
- An unvetted HBase library with four hidden timeout parameters compounded into a 20-minute blocking retry window, threatening to spawn 60,000 blocked threads per minute under a 1,000 req/sec load. (30:38)
Detailed notes
## Background Published on YouTube by ClojureTV on April 22, 2015, this presentation features Anthony Marcar discussing how WalmartLabs scaled Clojure across a multi-project enterprise application. The description highlights the scarcity of guides for large Clojure codebases and outlines the lessons and challenges faced while expanding the platform and team over three years.
## TL;DR Anthony Marcar shares practical architectural patterns from WalmartLabs' eReceipts platform, which ingests data from roughly 5,000 retail stores across 20 microservices and 70 Leiningen projects. He demonstrates how adopting Stuart Sierra's Component library eliminates brittle global state, organizes domain logic, and enables robust, channel-based testing without mocks. Marcar also tackles monorepo tooling hacks, warns of hidden network timeouts in third-party libraries, and outlines operational best practices including unified SLF4J/Logback logging, Apache Commons Daemon deployments, and infrastructure automation.
## Atmosphere & style The presentation carries the grounded, candid vibe of a seasoned engineer reporting from the production trenches rather than lecturing from theory. Marcar speaks with pragmatic humor and humility, laughing off an untimely Outlook chime and openly calling their monolithic project aggregation a 'zombie Frankenstein hack.' It feels relatable, clear-headed, and reassuring for developers grappling with real-world enterprise constraints and the messy realities of scaling on the JVM.
## Bulletin - [02:00](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=120s) WalmartLabs rebuilt their core eReceipts platform in Clojure across 20 microservices, 70 Leiningen projects, and 50,000 lines of code, sustained by a core team of only eight engineers. - [04:52](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=292s) The team eradicated ambient top-level state by adopting Stuart Sierra's Component library, allowing multi-instance database connections, deterministic lifecycle ordering, and isolated lexical passing. - [27:01](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=1621s) To bypass the Maven jar barrier in multi-project monorepos, WalmartLabs deployed an aggregation script that consolidates 70 distinct project.clj files into a synthetic root project for unified REPL workflows. - [30:38](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=1838s) An unvetted HBase library with four hidden timeout parameters compounded into a 20-minute blocking retry window, threatening to spawn 60,000 blocked threads per minute under a 1,000 req/sec load.
## Key takeaways - [00:00](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=0s) Operating Clojure at enterprise scale requires moving beyond trivial single-namespace tutorials to multi-project, distributed architectures. - [00:00](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=0s) Walmart's eReceipts platform ingests live transactions from roughly 5,000 retail stores, routing data to downstream features like Savings Catcher and InstaWatch. - [02:00](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=120s) Durable queues placed between producers and consumers safeguard the ingest pipeline from cascading failures when downstream services fail. - [02:00](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=120s) Embracing the mature Java and JVM ecosystem is indispensable for achieving high throughput, stability, and resilient tooling at scale. - [04:52](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=292s) Stuart Sierra's Component library eliminates brittle top-level global vars by structuring services and state into immutable records with explicit lifecycle methods. - [15:29](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=929s) Defining explicit component dependencies in a declarative system map allows running multiple distinct instances of a technology side-by-side without naming collisions. - [13:09](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=789s) Passing components strictly through lexical scope decouples business logic from connection implementations and eliminates race conditions in concurrent execution. - [20:48](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=1248s) Mocking external network side effects using core.async channels avoids brittle thread sleep calls and atom polling while enabling thread-safe parallel test runs. - [24:42](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=1482s) Because Component system topologies are plain data maps, integration tests can easily override real network boundaries by merging mock components over production constructors. - [27:01](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=1621s) Representing build configuration as Clojure data enabled a custom script to aggregate 70 project.clj files into a unified REPL session, resolving Emacs read-only jar navigation issues. - [30:38](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=1838s) Third-party libraries performing network I/O must be cynically vetted for hidden blocking threads, retry policies, and compound timeout behaviors. - [33:36](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=2016s) Routing all application, third-party, and Java library logging through SLF4J into Logback allows unified streaming to JSON files and real-time monitoring metrics. - [35:47](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=2147s) Pairing Apache Commons Daemon with Component provides a robust UNIX daemon architecture managed via jsvc while confining mutable state to a single system root reference. - [37:43](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=2263s) Automating infrastructure and service deployments via code and the REPL is essential to prevent operational fear and configuration drift across dozens of microservices.
## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=0s) Introduction to Clojure at Scale and the eReceipts Platform
Anthony Marcar, a Clojure engineer at WalmartLabs, introduces the realities of running Clojure in a large enterprise environment. Having worked with Clojure at Walmart for roughly three years, Marcar notes that operating at massive production scale is still an uncharted frontier for much of the Clojure community. The internet offers an abundance of tutorials and introductory guides showing how to build small, single-namespace applications or isolated libraries, but there is very little architectural guidance available on how to structure and scale large, multi-project systems. Marcar frames his talk not as an authoritative mandate on how everyone must write Clojure, but rather as an open sharing of the patterns, battle-tested solutions, and hard-earned lessons his team discovered along the way: “This is just our experiences and recommendations that we've come up with; everyone should be doing these things differently, it's a frontier, but anyway this is what we've come up with.”
The backbone of this experience comes from Walmart's eReceipts platform. From a customer-facing perspective, every paper receipt printed at a physical Walmart store includes a printed QR code at the bottom. This QR code encodes the unique identity of that specific transaction. Customers can scan the code using the Walmart mobile app (or any QR scanner) to download the digital receipt directly to their account, enabling paperless returns and ensuring physical paper slips are never lost. Beyond core receipt tracking, the platform serves as an internal data hub powering multiple high-profile Walmart customer initiatives:
- **Savings Catcher:** An automated price-matching system that compares in-store purchases against competing local stores and refunds the price difference to the customer. - **InstaWatch:** An integration that automatically grants customers digital movie copies when they purchase eligible physical DVDs or Blu-rays in stores. - **One-Hour Guarantee:** High-throughput processing supporting Walmart's massive holiday promotional sales (such as Black Friday events).
[02:00](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=120s) Data Center Architecture and System Scale
Behind the customer interface lies a massive backend data processing pipeline. As transactions occur across nearly 5,000 Walmart stores nationwide, every cash register checkout streams its receipt data into WalmartLabs data centers. The ingest pipeline functions as a one-way directed graph of data driven by side effects: persisting raw receipts into datastores, fanning data out to downstream internal systems, and triggering customer-facing push notifications. To survive the unpredictable environment of an enterprise data center where networks flutter and nodes disappear, the team places durable queues between producers and consumers. If an external platform dependency goes offline—such as Apple's push notification service—the ingestion queues absorb the pressure and isolate the pipeline from cascading outages.
```mermaid flowchart LR accTitle: WalmartLabs eReceipts Data Pipeline Architecture accDescr: Data flow showing store registers streaming receipts through durable queues into processing services that persist data and dispatch side effects.
subgraph Ingestion["Physical Stores"] Registers["~5,000 Store Registers"] end
subgraph Transport["Resilient Ingestion"] Q1["Durable Queue"] end
subgraph Core["Clojure Processing Services"] IngestService["Processing Engine"] end
subgraph Egress["Side Effects & Storage"] DB[("Cassandra Storage")] Partners["Walmart Internal Partners\n(Savings Catcher, InstaWatch)"] Push["Customer Push Notifications\n(Apple APNs / SMS)"] end
Registers --> Q1 Q1 --> IngestService IngestService --> DB IngestService --> Partners IngestService --> Push ```
Clojure is not confined to a single processing script; Marcar explains that the platform is “Clojure all the way down.” The team uses Clojure across the entire operational stack, including REST APIs for receipt querying, automated deployment pipelines, and operational monitoring. The system originated as an inherited legacy Java application from an acquired startup, which the team systematically rebuilt in Clojure. Over three years, a core team of approximately eight developers built out and maintained this infrastructure:
- **Store Coverage:** Ingests live transactional data from roughly 5,000 retail stores. - **Microservices:** Approximately 20 standalone backend services. - **Codebase Size:** Around 50,000 lines of Clojure code. - **Project Structure:** Composed of roughly 70 separate Leiningen projects. - **Team Size:** Maintained by approximately 8 engineers.
Marcar emphasizes that scaling involves learning how to embrace the JVM rather than fighting it. While Java code can feel verbose and painful to write, the maturity of the Java ecosystem is indispensable at high volume: “Java is painful to write, but there are a lot of awesome technologies there, and it's easy when you come in from a non-Java background kind of like be scared of all that Java stuff, but when you reach these kinds of scales, then Java becomes your friend.”
[04:52](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=292s) Eliminating Global State with Stuart Sierra's Component
When structuring production codebases, Marcar warns against a common anti-pattern prevalent in basic Clojure tutorials: declaring database connections or network sockets in top-level global vars using `def`, starting them via a manual function call, and having arbitrary business logic namespaces reference those ambient globals. In large systems, uncontrolled global state makes testing, REPL reloads, and lifecycle management brittle. Influenced by Stuart Sierra's talks on "Clojure in the Large," the WalmartLabs team eliminated top-level state by adopting Sierra’s Component library across all services: “Basically the recommendation I can give that has worked really, really well for us is embrace Component.”
Component provides a structured framework for managing the runtime state and lifecycle of application resources. Instead of mutating vars globally, resources are defined as Clojure records that implement the `com.stuartsierra.component/Lifecycle` protocol, exposing explicit `start` and `stop` lifecycle methods. Because Clojure data structures are immutable, `start` does not mutate an existing record in place; rather, it establishes the necessary runtime resource and returns a fresh copy of the record with that resource associated into it.
```clojure ;; Conceptual structure of a stateful Cassandra component (defrecord CassandraComponent [hosts keyspace connection] component/Lifecycle (start [this] (if connection this (let [conn (start-connection hosts keyspace)] (assoc this:connection conn)))) (stop [this] (if-not connection this (do (stop-connection connection) (assoc this:connection nil))))) ```
To ensure predictable system assembly, components follow strict conventions: - **Idempotency:** Calling `start` on a component that already has an initialized connection immediately returns the record unchanged. Similarly, calling `stop` on an unstarted component is a safe no-op. - **Explicit Lifecycle Flow:** Callers must always bind and pass the returned value of `(start component)` or `(stop component)`, preserving functional immutability rather than relying on mutable references. - **Constructor Factories:** Every component provides a helper constructor function that accepts an application-wide configuration map (parsed from an EDN file deployed to the physical server). The constructor extracts only the fields it requires—such as IP vectors or keyspace strings—keeping configuration parsing isolated from core business logic.
[09:24](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=564s) Categorizing Components: Stateful vs. Service Components
Marcar explains how the team categorizes components based on Stuart Sierra's taxonomy: stateful components, service components, and composite components. The Cassandra database handler is a classic stateful component because it directly owns a stateful runtime asset:
> “This is an example of a stateful component. It—there is some state. State could be a database connection, a thread pool, an atom, like anything that is inherently not—not immutable; even the core.async channel is state in some way, so that's what this does. This is encapsulating that state somewhere that we can think about and reason about and instantiate.”
By contrast, service components encapsulate communication boundaries with external systems. Marcar highlights an HTTP SMS component used by the platform to notify shoppers via text message. Service components wrap static network configuration details—such as remote API hosts, target ports, and endpoints—into a distinct lifecycle record. This mirrors the exact implementation patterns used across their production codebase, giving the team a clean, uniform mechanism to inject endpoints into processing pipelines without relying on hardcoded globals or environment lookups.
[10:32](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=632s) Stateless Service Components and Composite Business Logic
When interacting with external third-party services, a component might rely on a simple blocking HTTP client that provides no internal handle to a connection pool or mechanisms to stop and start. In the Stuart Sierra Component model, if a record has no managed state or lifecycle, it does not need to implement the `Lifecycle` protocol at all. When `component/start` or `component/stop` is invoked on such a record, Component simply detects that no lifecycle methods exist and returns the record unchanged. A concrete example is an SMS service component: it is defined as a simple record implementing a protocol with a method to send an SMS to a customer given a phone number and message body. Its constructor function merely takes a system configuration map, extracts the relevant `:sms` configuration sub-map, and instantiates the record.
```mermaid flowchart TD accTitle: Component Hierarchy in Uploader System accDescr: Composite Uploader component delegating to Cassandra and SMS components.
subgraph Composite ["Composite Component (Business Logic)"] U["Uploader Record\n- config: sms-text\n- dependencies: cassandra, sms"] end
subgraph Dependencies ["Underlying Service & State Components"] C["Receipt Cassandra Component\n(Stateful: DB Connection)"] S["SMS Service Component\n(Stateless: HTTP Protocol)"] end
U -->|"persists receipts"| C U -->|"sends notifications"| S ```
Building on raw infrastructure and service components, the next architectural tier is the composite component, which serves as the primary home for business logic. While underlying components manage raw state, network sockets, or service providers, composite components orchestrate these primitives to carry out domain workflows. For example, an `Uploader` composite component requires configuration (such as static SMS notification text) alongside references to other components—namely, a Cassandra component to persist uploaded receipts and an SMS component to notify customers. When defining the constructor for this composite record, Component’s dependency injection facilities specify that the uploader depends on an SMS component and a Cassandra component (aliased locally as `:receipt-cassandra`). These dependencies do not need to be instantiated beforehand; the system constructor handles resolving and injecting them into the record.
---
[13:09](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=789s) Eliminating Global State in Domain Functions
When implementing domain functions like `upload-receipt`, the system needs to persist receipt data to Cassandra and trigger an outbound SMS. Anthony Marcar highlights the divergence between common Clojure patterns and strict component-driven development by reflecting on WalmartLabs' earlier code:
> “This is kind of very typical code that you'll see around the Clojure community where it is assumed that there is a connection object in some namespace that has been started but we don't know.”
During the demonstration, an Outlook alert chimes on his laptop, prompting a brief laugh from Marcar: “I shouldn't go to that meeting... I thought I'd turned off Outlook... yeah we use Outlook at Walmart, so yeah.” Resuming his critique of implicit global vars, Marcar explains that calling functions that rely on ambient global connection objects introduces severe hidden complexity. Sockets might not have been initialized, they might have been closed by another thread, or race conditions might emerge across tests and worker routines.
```mermaid flowchart LR accTitle: Explicit vs Global Component Flow accDescr: Contrasting an uploader passing explicit lexical references versus relying on ambient global vars.
subgraph ExplicitPattern ["Component Pattern (Lexical Scope)"] direction TB Up["upload-receipt (uploader, receipt)"] Up -->|"destructures"| CComp["Cassandra Component"] Up -->|"destructures"| SComp["SMS Component"] CComp --> InC["insert-receipt(cassandra, receipt)"] SComp --> SendS["send-sms(sms, text, phone)"] end
subgraph AntiPattern ["Legacy Anti-Pattern (Ambient Vars)"] direction TB OldUp["old-upload-receipt (receipt)"] OldUp -.->|"implicit read"| GDB[("Global Cassandra Var")] OldUp -.->|"implicit read"| GSMS["Global SMS Client Var"] end ```
To eliminate these failure modes, WalmartLabs shifted to purely lexical passing of components. The modern `upload-receipt` function accepts the `uploader` record explicitly alongside the receipt data. Inside the function, destructuring extracts the Cassandra component, the SMS component, and the SMS template text. Sub-functions like `insert-receipt` and `send-sms` take only the specific sub-components they require:
- The calling function does not expose Cassandra internals or SMS implementation details to extraneous layers; it merely threads the scoped records down. - There is zero global state; all references remain strictly within the lexical scope of the function execution. - Eliminating implicit namespace vars prevents test pollution and shared mutable state collisions across application threads.
---
[15:29](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=929s) Declarative System Construction and Topology
The entire application graph is assembled inside a top-level system constructor function, such as `new-system`. While WalmartLabs’ production systems are roughly ten times larger due to dozens of interrelated services and stores, the structural mechanics remain identical. Within this constructor, symbolic keys are assigned to each component instance in a system map. This explicit mapping solves a major limitation of namespace-level singleton vars: the inability to run multiple instances of the same technology side by side.
```mermaid flowchart TD accTitle: System Component Dependency Graph accDescr: Topology of Cassandra, Redis, SMS, Uploader, and Customer API components.
subgraph Storage ["Databases & Caches"] RC["receipt-cassandra\n(:receipt DB)"] CC["customer-cassandra\n(:customer DB)"] RD["store-redis"] end
subgraph Services ["Service & Composite Components"] SMS["sms-service"] UP["uploader\n(Composite)"] API["customer-api\n(HTTP Service:8080)"] end
UP --> RC UP --> SMS API --> CC API --> RD ```
In Walmart's infrastructure, a single application frequently communicates with distinct database clusters simultaneously, requiring both a `:receipt-cassandra` component pointing to receipt keyspaces and a `:customer-cassandra` component pointing to customer keyspaces. Using records, creating multiple isolated connections is as simple as calling the constructor twice with different configuration maps. Furthermore, developers do not have to manually order component instantiation or startups:
> “Component will figure out the order of dependencies when they need to be started and stopped so all we need to do is say... we could swap all these around... there's nothing that we need to worry about here.”
Dependencies are wired declaratively using Component's dependency metadata (e.g., stating that `:customer-api` depends on `:customer-cassandra` and `:store-redis`). When startup is initiated, Component analyzes the dependency graph, constructs the topological order, and invokes `start` on upstream connections before starting downstream dependents (such as web servers binding to port 8080).
---
[17:43](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=1063s) REPL Demonstration: Starting Systems and Immutability
Switching to a live REPL, Marcar demonstrates the step-by-step lifecycle of a system using a representative configuration map containing hostnames, ports, and default message strings:
```clojure ;; Representative system configuration map {:sms {:host "sms.internal":port 8080} :sms-text "Thank you for shopping at Walmart!" :receipt-cassandra {:hosts ["10.0.0.1", "10.0.0.2"]:keyspace "receipts"} :customer-cassandra {:hosts ["10.0.0.3"]:keyspace "customers"}} ```
When evaluating `(new-system config)`, the REPL returns a plain, unstarted map of records:
- The `:receipt-cassandra` and `:customer-cassandra` records have host lists and keyspaces assigned, but their internal `:connection` fields remain explicitly `nil`. - The `:uploader` composite component contains its static `:sms-text`, but its `:cassandra` and `:sms` slots are unpopulated because injection occurs on startup. - The unstarted system is completely inert data, lacking active network sockets or side-effecting connections.
Executing `(component/start system)` transforms the graph into a running system. Pretty-printing the result shows that `:receipt-cassandra` contains an active Cassandra connection object, `:customer-cassandra` holds an entirely separate connection instance, and `:uploader` has had both the SMS component and receipt Cassandra component injected into its map fields.
Because Clojure relies on persistent data structures, invoking `start` does not mutate the original record map in place; it returns a new system record representing the running state while the unstarted instance remains untouched. Marcar points out that this structural immutability makes reasoning about systems straightforward and pays massive dividends in automated testing. By swapping out components in the system map prior to startup, tests can run side-effect validation—such as asserting that an upload invokes the receipt pipeline—without risking accidentally sending real SMS text messages to actual Walmart retail customers.
[20:48](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=1248s) Channel-Based Mocking Without Global State
When testing side-effecting code, traditional approaches often reach for mocking libraries or `with-redefs` to overwrite an outgoing function and replace it with a no-op. Anthony Marcar argues that relying on the standard Clojure toolbox yields far more power: “Mocking libraries can get pretty powerful, but if you can restrict yourself to the core Clojure ecosystem, like the core Clojure libraries, you're going to find yourself in a much more flexible area.” To illustrate this, he points to their SMS push notification component. In production, calling `send-sms` issues an HTTP POST to an external SMS provider who delivers the message to the customer. For tests, rather than instantiating the real HTTP component or monkey-patching it, they implement a test record that satisfies the same protocol but simply dumps the payload onto a `core.async` channel.
```mermaid flowchart LR accTitle: Production vs Mock Component Architecture accDescr: Diagram contrasting production SMS sending via HTTP with test SMS capture via core.async channel.
subgraph PROD["Production Flow"] P1["upload-receipt"] --> P2["SMS Component"] P2 -->|"HTTP POST"| P3["SMS Provider"] end
subgraph TEST["Test Flow"] T1["upload-receipt"] --> T2["Mock SMS Component"] T2 -->|"put! payload"| T3["async Channel"] T3 -->|"take! / blocks"| T4["Test Assertion"] end ```
While the team experimented with collecting results in an `atom` or using other synchronization primitives, channels proved vastly superior. Because their system is heavily asynchronous, queue-based, and concurrent, storing test results in an atom forced developers to poll at the end of the test or introduce brittle `Thread/sleep` calls to wait for side effects to land. With a channel, the test thread can cleanly block lower down, waiting directly on the channel until the operation emits a message. When the value pops out, the test immediately asserts against the phone number and SMS body text. Marcar notes one practical catch: if the side effect never runs, blocking on a bare channel will hang indefinitely, so wrapping the read with a one-second timeout is necessary sugar to fail the test fast if nothing arrives.
- Components are plain Clojure maps, so tests simply instantiate the uploader component and `assoc` the mock SMS channel component over the production dependency prior to running. - The entire test has zero global state; running tests concurrently requires only dynamic port incrementing to spin up 10 test systems in parallel without race conditions. - The lack of shared state makes the setup directly amenable to generative, property-based testing (like `test.check`'s `for-all`).
[24:42](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=1482s) Composing Whole-System Integration Tests with Component
Expanding beyond isolated components, Marcar explains how Stuart Sierra's Component library scales to large integration suites. In their production application, WalmartLabs manages approximately 50 components, many of which perform real-world external side effects. When running integration tests, developers do not want to trigger those side effects, so they construct a base system and selectively overlay a map of mock components for edge boundaries—such as SMS, Apple Push Notification service, and Google Cloud Messaging.
Because a Component system is just a record map, overriding behavior is a single line: developers invoke `merge` with the mock map directly over the production system constructor and then call `start`. Marcar praises the library's design minimalism: “Like Component is really nice like this, it's 200 something lines of code, most of which is comments, like it's really small, it's just kind of some dust around the edges of maps and records.” This pattern lets WalmartLabs execute full operational workflows (such as `upload-receipt`) against the entire system, verifying 99% of production code paths from end to end while keeping the third-party network boundaries safely mocked.
- Marcar advises: “I would highly recommend using Component if you're not already and you expect your codebase to scale in any way.” - Component provides a clean conceptual model for organizing namespaces, resolving the dilemma of where to put code: Cassandra operations (`insert`, queries) live in a Cassandra namespace alongside the Cassandra component. - Structuring components around state provides natural boundaries when the time comes to decouple a monolith into multiple projects.
[27:01](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=1621s) The Monorepo Dilemma: Leiningen and the Read-Only Buffer Problem
When an application expands across multiple Leiningen projects, developers inevitably hit painful local workflow friction. In Emacs/CIDER, jumping to the definition of a local shared library (like an internal HTTP library) opens the file in a read-only buffer because the JVM runtime and Leiningen resolve that dependency as a packaged jar inside `~/.m2/repository`, rather than pointing to the active source directory. “Who actually has hit this?” Marcar asks the room to a flurry of nodding heads: “Yeah, it gets really annoying, and this is a side effect of having a Maven repository and Java, which Clojure is based upon.”
```mermaid flowchart TD accTitle: Project.clj Aggregation Hack accDescr: Flow showing multiple individual project.clj files combined into a single Frankenstein project.clj.
subgraph SOURCES["Individual Projects (~70)"] P1["Project A: src & deps"] P2["Project B: src & deps"] P3["Project N: src & deps"] end
SOURCES -->|"Parse & aggregate data"| GEN["Generator Script"] GEN -->|"Emits unified data"| FRANK["Aggregated 'Frankenstein' project.clj"] FRANK --> REPL["Single Unified REPL Session"] FRANK --> SUITE["Run All Tests Across Entire System"] ```
Because Clojure treats `project.clj` configurations as data rather than opaque XML or imperative build scripts, the team worked around the Maven jar barrier with what Marcar calls a “zombie Frankenstein project.” A custom generator script crawls the entire repository directory tree, pulls in every individual `project.clj`, merges their source paths, resource paths, and dependency vectors, and outputs a massive synthetic root configuration containing all 70 source directories.
- The consolidated root project lets developers access every piece of source code across the entire codebase in a single interactive REPL session. - System-wide test suites can be executed inside one unified run without having to build and install intermediary jars to Maven. - While Marcar admits that “it's a total hack,” he highlights that representing build definitions as Clojure data makes such radical manipulation practical, in stark contrast to the rigidity of Java build tooling.
[30:09](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=1809s) Evaluating Dependencies: The Need for Cynicism
As applications expand in scope and traffic, selecting third-party libraries becomes a critical operational challenge. Rather than adopting libraries eagerly, Marcar stresses adopting a posture of extreme skepticism: “The fundamental thing is just be extremely cynical, and yeah, Rich Hickey said this best: ‘Not everything is awesome.’” While the Clojure ecosystem offers an abundance of creative community projects, enterprise scale demands rigorous scrutiny before introducing any external dependency into the codebase.
[30:38](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=1838s) Evaluating Third-Party Libraries and the Pitfalls of Network I/O
When deciding whether to adopt a third-party Clojure library, developers must scrutinize the design decisions behind it. Anthony Marcar notes that one must ask whether a library enforces a rigid idiom or relies heavily on macros, because those choices compound over time and determine whether engineering teams end up writing endless boilerplate wrappers just to work around external constraints. For simple, pure tasks—such as parsing CSV files where a library performs pure input and output without network connections or state—pulling in an external dependency is perfectly fine. Libraries that introduce custom idioms can also be worthwhile if the problem domain demands it; `core.async` imposes a distinct programming model, but WalmartLabs happily embraced it because it solved concurrency challenges that were otherwise intractable.
The critical danger arises with libraries performing network operations. Using the hypothetical example of a convenience library that takes a data structure and writes a file to Dropbox, Marcar warns that teams must investigate how the library handles queueing, whether it relies on blocking or non-blocking I/O, and whether it provides explicit timeout guarantees. Under heavy traffic, missing these details can be catastrophic. At WalmartLabs, an unvetted library interacting with Apache HBase caused severe operational distress. Although WalmartLabs frequently handles 1,000 transactions per second during peak events like Black Friday, this incident occurred outside peak season when a network degradation slowed transfers down to roughly 100 bytes per second.
The team believed they had protected themselves by setting a one-second timeout. In reality, the library contained four additional undocumented timeout parameters. Instead of typical socket or connection timeouts, it had an opaque retry loop that hid failures from the application. The compound effect inflated the default timeout window to 20 minutes across blocking threads. With 1,000 requests arriving every second, the application was rapidly accumulating 60,000 open, stalled threads within a minute. Emphasizing the need for deep skepticism when traffic begins to scale, Marcar advises: “The second you think that you're going to be scaling then just get I mean just be cynical about libraries it helps a lot.”
- **Safe dependencies**: Small, stateless, single-purpose libraries with no network or I/O footprint (e.g., CSV parsers). - **Network library vetting checklist**: - Does it use non-blocking I/O or blocking threads? - How are internal queues managed under backpressure? - What are the connection, socket, and retry timeout guarantees? - Does an internal retry policy mask errors and multiply wait times? - **HBase incident impact**: A hidden 20-minute composite timeout combined with blocking calls threatened to create 60,000 stalled threads per minute at 1,000 requests per second.
---
[33:36](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=2016s) Unifying Java Logging with SLF4J and Logback
Navigating the fragmented Java logging ecosystem can be intimidating for Clojure engineers, yet Marcar insists that understanding it is essential for operating high-throughput production services. Clojure applications rely heavily on Java libraries under the hood—popular HTTP clients like `clj-http`, for instance, wrap Apache HttpComponents. When issues arise in production, engineers must be able to debug those underlying Java components directly through their logging output. Marcar stresses the importance of mastering this layer: “If you don't understand how this works then you're going to be able to bug to bug this in production so yeah like understand it it's worth it it gives you a lot of power.”
Rather than struggling with disparate configurations for Logback, Log4j, and `java.util.logging`, WalmartLabs routes all logging calls through SLF4J (Simple Logging Facade for Java) to Logback. Standardizing on this facade provides runtime flexibility without altering application logic. WalmartLabs configures custom Logback appenders to output structured JSON to disk for Logstash ingestion into centralized aggregation clusters, while simultaneously transmitting UDP packets to a Riemann server for real-time monitoring.
```mermaid flowchart TD accTitle: Logging Unification Architecture accDescr: Application and library log calls routed through SLF4J and Logback to files and metrics servers. subgraph Sources["Log Producers"] CLJ["Clojure App Code"] JUL["java.util.logging"] JLIB["Underlying Java Libs (e.g., Apache HTTP)"] end
subgraph Facade["Unified Logging Facade"] SLF["SLF4J Routing Layer"] LB["Logback Engine"] end
subgraph Appenders["Logback Appenders"] JSON["JSON File Appender\n(for Logstash)"] UDP["UDP Appender\n(for Riemann Metrics)"] end
CLJ --> SLF JUL --> SLF JLIB --> SLF SLF --> LB LB --> JSON LB --> UDP ```
Running out of presentation time, Marcar refers attendees to his GitHub repository for concrete code snippets showing how to wire these adapters into `project.clj`. Consolidating configuration prevents operational drift and eliminates the chaos of maintaining separate `log4j.xml`, `logback.xml`, and `logging.properties` files in the same repository.
---
[35:47](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=2147s) Daemonizing Applications with Apache Commons Daemon and Component
Deploying an application typically culminates in packaging an uberjar and running it reliably on a host system. Rather than reaching for complex application servers like Apache Tomcat, WalmartLabs manages long-running standalone processes using Apache Commons Daemon and its `jsvc` binary wrapper. Commons Daemon enforces a lean Java interface containing four lifecycle methods:
```mermaid flowchart LR accTitle: Commons Daemon Lifecycle accDescr: Commons Daemon lifecycle progression through init, start, stop, and destroy states. I["init()"] --> S["start()"] S --> ST["stop()"] ST --> D["destroy()"] ```
This four-method lifecycle pairs naturally with Stuart Sierra’s Component library. During `init()`, the process reads configuration from the filesystem and constructs the dependency injection system graph. To satisfy Commons Daemon's design—which requires initializing the system in one hook and executing it in another—WalmartLabs maintains a single mutable root var holding the system reference. Marcar highlights this pragmatic exception to functional purity: “We have no global state in our system except for this one system reference and the only reason we have that is because Commons daemon calls init first and assumes that you mutate some global state to create the system so that afterwards it can start that system.”
When Commons Daemon calls `start()`, the application alters the root var by calling `component/start` across the assembled map, starting every database connection, queue consumer, and web server in dependency order. Shutting down cleanly uses the matching `stop()` and `destroy()` methods. Operators control the resulting process using `jsvc`, which handles UNIX daemon mechanics natively:
- **Configurable process user**: Drops root privileges to run as a restricted service account. - **Standard streams redirection**: Directs standard out (`stdout`) and standard error (`stderr`) to target files. - **JVM property injection**: Passes classpath and system flags directly to the underlying virtual machine. - **Lifecycle invocation**: Targets the specific compiled class implementing the Commons Daemon interface.
---
[37:43](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=2263s) Infrastructure as Code and Recommended Resources
As an organization grows from a single service to a distributed architecture, manual server administration becomes unsustainable. Moving an uberjar via SCP to an individual host works for early prototypes, but Marcar points out that managing 20 distinct microservices spread across hundreds of nodes alongside load balancers, messaging queues, databases, and monitoring clusters demands full automation. If tasks such as configuring load balancers, provisioning host capacity, and templating `logback.xml` require manual intervention, teams will dread launching new systems. Marcar cautions: “If you're doing that via hand you're going to be scared of creating new services it's gonna be like a time consuming thing full of you know errors automate as much as that as possible as early as possible and it'll make your lives a lot easier.”
At WalmartLabs, the entire infrastructure configuration and deployment pipeline is automated and driven directly from a Clojure REPL. While declining to endorse the exact orchestration technology in a public forum—inviting interested engineers to speak with him privately after the talk—Marcar emphasizes that automating everything empowers developers to assemble components into new deployable uberjars rapidly.
To conclude, Marcar highlights four foundational resources that shaped the WalmartLabs architecture and engineering culture:
- ***Release It!* by Michael Nygard**: A book on designing resilient production software. Marcar remarks: “It'll scare the crap out of you when you're thinking about creating big systems because it tells you all the bad things that can happen but you should read it.” - **"Component" by Stuart Sierra**: A recorded talk providing deep conceptual context on structuring Clojure systems using lifecycle components. - **"The Language of the System" by Rich Hickey**: A presentation that formed the core philosophy behind the decoupled, message-driven system architecture at WalmartLabs. - ***The Phoenix Project***: A recommended starting point for developers looking to understand modern DevOps practices and infrastructure as code.
## Notable quotes > [20:48](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=1248s) Mocking libraries can get pretty powerful, but if you can restrict yourself to the core Clojure ecosystem, like the core Clojure libraries, you're going to find yourself in a much more flexible area. — Anthony Marcar
> [02:00](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=120s) Java is painful to write, but there are a lot of awesome technologies there, and it's easy when you come in from a non-Java background kind of like be scared of all that Java stuff, but when you reach these kinds of scales, then Java becomes your friend. — Anthony Marcar
> [24:42](https://www.youtube.com/watch?v=av9Xi6CNqq4&t=1482s) I would highly recommend using Component if you're not already and you expect your codebase to scale in any way. — Anthony Marcar
## Mentioned Anthony Marcar, WalmartLabs, Walmart, Clojure, Java, JVM, eReceipts, Savings Catcher, InstaWatch, One-Hour Guarantee, Cassandra, Apple APNs, Leiningen, Stuart Sierra, Component, Redis, core.async, test.check, Google Cloud Messaging, Emacs, CIDER, Maven, Rich Hickey, Apache HBase, SLF4J, Logback, Log4j, clj-http, Apache HttpComponents, Logstash, Riemann, Apache Commons Daemon, jsvc, Release It!, Michael Nygard, The Phoenix Project
## Video description There are many resources to help you build Clojure applications. Most however use trivial examples that rarely span more than one project. What if you need to build a big clojure application comprising many projects? Over the three years that we've been using Clojure at WalmartLabs, we've had to figure this stuff out. In this session, I'll discuss some of the challenges we've faced scaling our team and code base as well as our experience using Clojure in the enterprise.
[Source](https://www.youtube.com/watch?v=av9Xi6CNqq4)