Ontology engineering · a working brief

Build it in modules, not one block.

Why an ontology should be a small set of layered, independently-versioned modules with a clean dependency graph — and how to draw the seams.

Written for Data analysts Ontology maintainers growing a model

The argument, up front

Thesis

A monolithic ontology is a single file nobody dares touch. A modular one is a set of small, layered pieces — each understandable alone, reusable elsewhere, and safe to change without breaking the rest. Draw the boundaries once, and the whole model stays maintainable as it grows.

Left unstructured, a domain model accretes into one enormous graph where everything references everything, no one can hold it in their head, the reasoner slows to a crawl, and a change to claims quietly perturbs policies. Modularization is the same discipline that keeps software codebases sane — packages, dependencies, bounded contexts — applied to knowledge. A healthy ontology follows it from the start; this brief explains why, and how to keep doing it well.

It completes a trilogy of working briefs. Start with the questions says what should drive each module; Model the domain, not the message says what must not shape it; this one says how to structure the modules into a whole.

01 — DEFINITION

What a module is

An ontology module is a self-contained slice of the model with its own IRI, its own namespace, its own version, and an explicit list of the other modules it depends on. It is the ontology’s equivalent of a package or a bounded context: a cohesive unit you can build, reason over, version and own on its own.

The bridge to what you already know

In software / data engineeringIn the ontology worldSame idea?
Package / libraryOntology module (a .ttl file)A cohesive, named unit
Bounded context / schemaA domain module — policy, claimA slice of the world
import / dependencyowl:imports“I need this to work”
Public API / interfaceFoundation vocabulary (core:)The shared contract
Semantic versionowl:versionIRI · versionInfo“This release of the piece”
Dependency graph (must be acyclic)The owl:imports graph (also acyclic)No circular references

A modular ontology, in three layers

The Chubb ontology is already organised this way — each layer imports only the ones above it:

Layer 1 · foundation · most stable

fnd/ — the upper ontology & vendored standards

core, agreement, identifier, location, and vendored W3C vocabularies (org, time, skos). Everything ultimately roots here.

▲   imports   ▲

Layer 2 · domain

ins/ — the insurance domain

policy, claim, coverage, business. Each imports the foundation; policy also composes claim and coverage.

▲   imports   ▲

Layer 3 · meta & instances

misc/ & resource/ — governance & sample data

Competency questions (competency, policy-cq) and A-Box individuals kept out of the schema. They consume the domain; nothing depends on them.

02 — THE CASE

Why modularize?

Every benefit below compounds as the model grows. A monolith is tolerable at ten classes and unmanageable at a thousand; modules keep the marginal cost of each addition flat.

comprehension

It fits in your head

A module is small enough to read, review and reason about in one sitting. You understand claim without loading the entire enterprise model — the single biggest aid to newcomers.

reuse

Foundations get reused, not copied

Import core, time or org instead of re-modelling parties and intervals in every project. The vendored W3C standards are reused as-is; Chubb axioms live in core.

evolution

Modules evolve independently

Each carries its own owl:versionIRI. You can revise claim to 0.2 and publish it without forcing a lockstep re-release of policy or the foundation.

reasoning

The reasoner stays fast

A DL reasoner works over a module’s import closure, not the universe. Load only what a task needs and consistency checks stay tractable — and you can pick a profile per module.

ownership

Teams can own a slice

Clear seams mean clear stewardship. Underwriting can own policy while claims owns claim, and both evolve in parallel behind a shared foundation contract.

testability

Each module has its own tests

Competency questions cluster by module, so each has a bounded acceptance suite. A change is “done” when its module’s CQs still pass — a local, checkable finish line.

One block vs. a layered set

One monolithic ontology

  • Everything couples to everything. No boundary stops a claims edit from altering policy inferences.
  • No reuse. Parties, time and money are re-modelled per project instead of imported.
  • All-or-nothing versioning. Any change re-releases the whole thing; consumers can’t pin a stable part.
  • The reasoner loads it all. Every consistency check pays for axioms it never uses.

A layered set of modules

  • Coupling is explicit. Dependencies are exactly the owl:imports edges — visible and few.
  • Foundations are shared. core, org and time are imported once and reused everywhere.
  • Independent releases. Each module versions on its own clock behind a stable contract.
  • Bounded reasoning. Load a module’s closure and no more; checks stay fast and local.
03 — THE DEPENDENCY GRAPH

Keep the imports directed and acyclic

The one rule that makes all the benefits hold: dependencies must form a directed acyclic graph. If policy imports claim, then claim must never import policy. A cycle collapses two modules into one indivisible blob — you can no longer load, version or reason about either alone.

dcterms skos org time core agreement coverage claim business policy
Chubb module vendored W3C standard imports a module imports a standard
Every edge runs upward. A module imports only things in a layer above it, so no path can ever return to where it started — the graph is acyclic by construction. policy composes agreement, coverage and claim; a claim links back to its policy through policy:underPolicy as data, never as an import — which is how the old policy ↔ claim cycle was broken.

Data can point back; imports cannot

A common worry: “but a claim refers to its policy — isn’t that a cycle?” No. The reference is a triple (someClaim policy:underPolicy somePolicy) that lives in the data and uses a property defined in policy. The claim module never imports policy to say it. Runtime links between individuals are free to form any shape; only the module import graph must stay acyclic.

04 — DRAWING THE SEAMS

How to draw the boundaries

Good modules are not arbitrary file splits. They follow a few principles that maximise cohesion inside each module and minimise coupling between them.

  • Cluster by competency question. Group the questions the business asks; each tight cluster is a module. The seams reveal themselves — Policy, Claim, Coverage, Party. (This is the direct payoff of the questions-first method.)
  • Layer by stability. Put the concepts that rarely change — parties, time, money, identity — in a foundation everything imports. Let the volatile, product-specific concepts live in domain modules on top.
  • Depend on the contract, not the internals. A domain module should lean on foundation vocabulary (core:), not reach into a sibling’s private classes. Shared meaning belongs in the layer below, not smuggled sideways.
  • Keep imports directed and minimal. Import only what you actually use, and never create a back-edge. If two modules want to import each other, the shared part belongs in a third, lower module.
  • Mirror the metadata. When you add an owl:imports, update the # imports: header and the prefixes too — the declared dependencies and the real ones must always agree.
05 — THE PITFALLS

Modularize well, not just a lot

Modularization is a means, not a score to maximise. Three failure modes turn the discipline against itself.

Pitfall 1 — over-fragmentation

Splitting into dozens of tiny, chatty modules trades one problem for another: the import graph itself becomes the thing nobody understands, and every task drags in a long tail of dependencies. Right-size the pieces — a module should be big enough to answer a coherent cluster of questions, small enough to hold in your head. Not one blob; not a thousand shards.

Pitfall 2 — orphan modules

A module no one imports is dead weight — and a trap, because a newcomer may build on it by mistake. A model may accumulate one: fnd/identifier.ttl sits in a legacy namespace that no ins/ module imports. Prefer core:Identifier / dct:identifier. Either wire an orphan in, or retire it.

Pitfall 3 — a churning foundation

The foundation earns its place by being stable; if core changes every week, every module downstream is destabilised at once. Push volatility upward into the domain layer and keep the base deliberately slow-moving. A foundation edit should be a considered, well-reviewed event.

The one habit to keep

Before adding anything, ask which module owns this concept? — and before adding an owl:imports, ask does this edge point strictly upward? Those two reflexes, applied every time, are most of what keeps a growing ontology from collapsing back into a monolith.