on the clojure move

jump to main content

Systems Don't Exist But Definitions Do

1. Fish Don't Exist (And Neither Does Your Code)

You've probably heard it: "Fish don't exist."

Not in the sense that there are no aquatic creatures. But in the sense that "fish" is a fuzzy, human-invented category with no clean boundaries. Lungfish are fish? Are whales fish?

The answer: Fish exist because we decided they do. We drew a line around a collection of creatures and said "that's a fish." The category is useful, but it's not discovered in nature—it's constructed by us.

2. Files Don't Exist Either

Your computer feels real. Files, folders, applications—you can see them on your desktop.

But they don't actually exist.

What actually exists: magnetic patterns on disk, electricity flowing through circuits.

What doesn't exist: the "file" abstraction.

When you double-click "myproject.txt", the OS executes complex instructions to read bytes and present them as a "file." The "file" is a definition. A useful fiction. If the OS changed its definition, the whole concept changes.

3. Your System Doesn't Exist Either

Now extend this to your software.

You believe your system has an OrderService, an order_created event, a payment-timeout error. But these don't actually exist.

What actually exists: functions executing, data transforming, bytes transmitting.

What doesn't exist: the categories you've imposed.

When you say "OrderService," you're drawing a circle around clustered functions. That's a definition, not a discovery. Tomorrow you could reorganize it completely.

If nothing fundamentally exists except the definitions we create, then the definitions themselves become the most important part of the system.

4. The Usual Mess

Most teams scatter definitions everywhere:

  • Component: class in code, deployment in K8s, box in diagram, paragraph in wiki
  • Error: exception in code, ticket in Jira, Slack thread, tribal knowledge
  • Event: Kafka schema, consumer code, API docs, that one email

Four definitions of the same thing. All drifting. All inconsistent.

5. Make Definitions Explicit

Stop pretending definitions are just "documentation." They're not describing the system—they are the system.

;; First, define what errors need
(s/def :error/code string?)
(s/def :error/severity #{:low :medium :high :critical})
(s/def :error/retry? boolean?)

;; Define the error TYPE and the required props 
(contract.type/def #{:semantic-namespace/error}
  [:error/code :error/severity :error/retry?])

;; Now define a specific error with expected props-types/values
(contract/def #{:semantic-namespace/error 
                :domain.payment/timeout}
  {:error/code "PAY_TIMEOUT"
   :error/severity :high
   :error/retry? true})

Notice what just happened: The identity #{:semantic-namespace/error :domain.payment/timeout} reads like English:

"An error, in the payment domain, specifically a timeout"

The compound identity is the description. You don't need separate documentation—you can read it.

6. Why Compound Identities Matter

Traditional identities are flat strings. No semantics. No relationships.

:payment-timeout-error  ;; What domain? What type? What properties?

Compound identities are sets of semantic aspects. Readable as plain English.

#{:semantic-namespace/error      ;; "an error"
  :domain.payment/timeout        ;; "payment domain, timeout type"  
  :severity/high}                ;; "high severity"

;; Reads: "A high-severity error in the payment domain, specifically a timeout"

Now the system can answer questions:

;; "What can go wrong with payments?"
(find-docs #{:domain.payment})

;; "What are all high-severity issues?"  
(find-docs #{:severity/high})

;; "Show me all errors"
(contract.type/instances #{:semantic-namespace/error})

The definitions are queryable. The identities are readable. The system is self-describing.

7. The Scale: What Gets Defined?

You might think: "Okay, but how many contract types do I really need?"

More than you think. And that's the point.

7.1. Short Term (First Sprint)

;; Core types - 5-8 contract types
(contract.type/def #{:semantic-namespace/error} [...])
(contract.type/def #{:semantic-namespace/event} [...])
(contract.type/def #{:semantic-namespace/endpoint} [...])
(contract.type/def #{:semantic-namespace/component} [...])
(contract.type/def #{:semantic-namespace/permission} [...])

7.2. Mid Term (Growing System)

;; The system reveals itself - 20-40 contract types
(contract.type/def #{:semantic-namespace/metric} [...])
(contract.type/def #{:semantic-namespace/command} [...])
(contract.type/def #{:semantic-namespace/query} [...])
(contract.type/def #{:semantic-namespace/job} [...])
(contract.type/def #{:semantic-namespace/validation} [...])
(contract.type/def #{:semantic-namespace/transformation} [...])
(contract.type/def #{:semantic-namespace/webhook} [...])
(contract.type/def #{:semantic-namespace/cache-strategy} [...])
(contract.type/def #{:semantic-namespace/rate-limit} [...])
(contract.type/def #{:semantic-namespace/circuit-breaker} [...])
(contract.type/def #{:semantic-namespace/retry-policy} [...])
(contract.type/def #{:semantic-namespace/saga} [...])
(contract.type/def #{:semantic-namespace/feature-flag} [...])
(contract.type/def #{:semantic-namespace/configuration} [...])
(contract.type/def #{:semantic-namespace/migration} [...])
(contract.type/def #{:semantic-namespace/integration} [...])
(contract.type/def #{:semantic-namespace/alert} [...])
(contract.type/def #{:semantic-namespace/dashboard} [...])
(contract.type/def #{:semantic-namespace/report} [...])
(contract.type/def #{:semantic-namespace/audit-log} [...])

7.3. The Realization

Each type might have 5-50 instances:

;; 15 errors across 4 domains = 15 definitions
#{:semantic-namespace/error :domain.payment/timeout}
#{:semantic-namespace/error :domain.payment/declined}
#{:semantic-namespace/error :domain.order/not-found}
#{:semantic-namespace/error :domain.order/invalid-state}
#{:semantic-namespace/error :domain.shipping/address-invalid}
;; ... etc

;; 25 events across your system = 25 definitions
#{:semantic-namespace/event :domain.order/created}
#{:semantic-namespace/event :domain.order/cancelled}
#{:semantic-namespace/event :domain.payment/succeeded}
;; ... etc

;; 40 endpoints = 40 definitions
#{:semantic-namespace/endpoint :api.order/create}
#{:semantic-namespace/endpoint :api.order/get}
#{:semantic-namespace/endpoint :api.payment/charge}
;; ... etc

A mid-sized project easily has 500-1000 definitions across 30-40 contract types.

Right now, these definitions are scattered. In your head. In code. In tickets. In Slack.

What if they were all in one place? Typed. Queryable. Readable.

;; "Show me the entire system"
(count @registry)  
;; => 847 definitions

;; "What domains do we have?"
(keys (clusters))
;; => (:domain.payment :domain.order :domain.shipping :domain.user)

;; "What can fail in production?"
(->> (contract.type/instances #{:semantic-namespace/error})
     (map contract/fetch)
     (filter #(= :critical (:error/severity %))))
;; => All critical errors, across all domains

;; "What needs monitoring?"
(contract.type/instances #{:semantic-namespace/metric})
;; => Every metric defined in the system

The scale isn't a burden. It's clarity.

Every one of those 847 things already exists in your system—you just haven't named them explicitly yet.

8. The Leap

Most teams think: System exists → document it → hope docs stay current

The truth: Define everything explicitly → system implements the definitions → query the definitions to understand the system

The definitions come first. Everything else flows from them.

9. What You Get

;; Single source of truth
@registry  
;; Every metric, error, event, permission, component

;; Fully queryable
(contract.type/instances #{:semantic-namespace/error})
;; All errors, across all domains

;; Human readable  
#{:semantic-namespace/metric :domain.order/latency}
;; "A metric for order domain latency" - reads like English

;; Generative
(generate-api-from-definitions)
(generate-docs-from-definitions)  
(generate-tests-from-definitions)

Your system isn't mysterious anymore. It's not hidden in code, configs, or people's heads.

It's explicit. It's unified. It's readable. It's just definitions.

All the way down.

10. resources