What is domain-driven development?

Domain driven development means we are highly focused on solving the core problems within a domain. Say for example we are building an app for a taco delivery service. Delivering tacos would be our domain. Our domain experts, people who understand the domain, are a resource we can use to understand how a system we are building should work. In this example we could say a taco delivery person and the business owner are domain experts. If we ask them what the system can do, they may respond with something along the lines of, "A user can order a taco online, our cook receives the order, makes the tacos, gives it to the delivery person and they can use the app to get the address and deliver the tacos."

A common problem when developing solutions using traditional means for development is that we create many new layers of vocabulary between the domain problem we need to solve and the code required to solve it.

For instance product may come to us with a straight forward task:

🎯 Venue managers should be able to add menu items to a proposal

Then somewhere in our development cycle it stops being about adding menu items to a proposal and instead becomes about trying to dispatch an action from our UI to our store and then triggering an async server request to submit a serialized JSON request to store the data, then deserialize the response into our app store through our reducers so an atomic update is sent back to the component responsible for the proposal. If that doesn't make sense to you, that is exactly the problem.

While it is understandable by studying the code and can be documented, it means that the only experts on this can be other developers and most often only the original developer. Some of that is unavoidable but what if we could write code in a way that reads more naturally, so that a domain expert and a developer can use it as a reference for how a system should work to solve a real problem?

Clojure and domain-driven development

Functional programming often leads to code that can directly reflect the domain problem we're trying to solve. Part of that is how naturally real problems can be modeled in functions. Functions represent actions and so when crafting a system to help users add menu items to a proposal, you can have a function like (add-item-to-proposal item proposal). In many traditional tools you end up having to define actions against a noun like Proposal.items.add but what gets lost is that the context may change. Adding items to a proposal may mean a different behavior between how a Venue Manager adds items versus how a booker adds items to a proposal. How do you express that? In traditional development you will likely add more names so we end up with VenueProposal.items.add and BookerProposal.items.add. Alternatively you may namespace the method name, or add an argument like Proposal.items.add(item, venue_manager=True). Now what happens if we want to add another context? Our definition of Proposal grows in complexity trying to accommodate for all contexts it may serve. In Clojure we get to leverage namespaces which can then serve as our context. Instead of creating a method, we would create a file like this:

(ns app.proposals.venue-manager)

(defn add-item
  "Takes a proposal object and an item and appends it to the items"
  [proposal item]
  (update proposal :items conj item))

This means we would be creating a file src/app/proposals/venue_manager.clj and defining our functions to operate on proposals in the venue_manager context. Adding new contexts is easy and simple because we only have to create new files. Check out Clojure namespaces [future link] for how to call it within Clojure or ClojureScript.

The best part is this setup is not exclusive to Clojure so that we can approach problems like this in languages like Python, Ruby, JavaScript, but what's helpful is that this is the expected default in Clojure where as in the other languages we would be relying on discipline and establishing conventions.

Pipelines communicate high-level flows