Modeling Domain Types with `defrecord` and `deftype`

Use `defrecord` and `deftype` deliberately in Clojure, and know when plain maps or tagged data are still the better model.

Domain type: A named representation for data with stable structure or behavior expectations inside a system.

Clojure developers sometimes reach for defrecord or deftype because they want stronger modeling than an anonymous map. That can be the right move. It can also be the wrong one if the real need is just a tagged map and a few clear functions.

The important thing to understand is that Clojure does not give you ML-style algebraic data types directly. defrecord and deftype are useful, but they solve different problems.

defrecord Is Structured Data That Still Feels Like Data

defrecord works well when you want:

  • named fields
  • ordinary key-based access
  • interoperability with map-like operations
  • optional protocol implementation
1(defrecord User [id email roles])
2
3(defn active-admin? [user]
4  (contains? (:roles user) :admin))

Records are often a good fit when you want a recognizable domain shape without abandoning Clojure’s data-first style.

deftype Is Lower Level And More Specialized

deftype is better when you care about:

  • tighter control over representation
  • protocol or interface implementation
  • performance-sensitive custom types
  • behavior that does not need map-like semantics

It is not the usual first choice for application modeling. It is a more specialized tool.

Plain Maps Often Stay Better

A lot of domain modeling in Clojure is still best expressed as tagged maps:

1{:kind :email-notification
2 :recipient "user@example.com"
3 :subject "Welcome"}

That works well because maps are easy to inspect, serialize, transform, validate, and extend. If the value mostly moves through functions and data pipelines, a map plus clear invariants may be all you need.

Modeling Variant Cases

When developers ask for algebraic data types, they often want sum types such as “this value is one of several cases.” In Clojure, that is usually modeled with:

  • a distinguishing keyword such as :kind or :type
  • case, cond, protocols, or multimethods for behavior dispatch
  • specs or validation rules for each variant

The design goal is not to imitate another language’s syntax. It is to preserve clear variant boundaries in ordinary Clojure data.

Design Review Questions

When reviewing domain types in Clojure, ask:

  • Is a plain map already enough?
  • Does a record improve clarity, identity, or protocol integration?
  • Is deftype justified by performance or integration needs?
  • Are variant cases modeled clearly with explicit tags and validation?

Good Clojure modeling keeps the data legible first. Named types should make the model clearer, not just more formal.

Loading quiz…
Revised on Thursday, April 23, 2026