Field notes · 7 parts · Free to read

The producer module inventory.

Four questions per module, five places a module can land, and the one count that predicts the work.

For anyone being asked what the estate looks like on the other side of ODC, before the plan exists.

On this pageWhy modules and not applications

A compatibility scan answers one question well: which applications can be converted. It does not answer the one the architect is holding, which is what the estate becomes. That answer is decided module by module, in the producers, and it is decided by things no scan reads.

This is the inventory we build to get it. It is not clever. It is a sheet with one row per producer module and a handful of columns, and the value is entirely in having filled it in before anyone writes a number down.

Part 1

Why modules and not applications

In O11 a module is the unit of reuse, the unit of deployment, the unit of ownership and the place the data lives, all at once. ODC separates those jobs. Entities live in apps. Libraries hold reusable logic and cannot touch entities. Public server actions only come from libraries. Entities an app makes public are read-only to everything outside it.

So an application converts, more or less, as a container. The modules inside it do not. Each producer has to be placed on the other side, and the placement is a design decision made against the four rules above. Applications are what the scan counts. Producers are where the months are.

Part 2

Building the list

  • Start in Service Center, not Service Studio. Count applications, count modules, then pull the module list and mark which ones have consumers. Anything with at least one consumer is a producer and gets a row. End-user modules that nothing references do not, however large they are. They convert as they stand.
  • Then Discovery, for what points at what. Module to module is enough at this stage. You want, for each producer, the list of consumers and whether the reference is to an entity, a public action, a block or a structure. Discovery gives you the first two levels reliably. The element-level detail comes later, by opening consumers.
  • Do not skip the small ones. The row that costs the most time is rarely the big core module with the documented owner. It is the shared module with one entity and two actions that sits under half the factory because it was the easiest place to put something several years ago. Those get rows too. Especially those.

Part 3

The four questions

Ask them of every producer, in this order, and write the answers on the row.

  • Does it own entities? If yes, it is anchored to an app. Everything else in the module is negotiable. That is not.
  • Do consumers write to those entities, and how? Through the module’s own public actions, or directly against the public entity from their own logic? Direct cross-module writes are common in estates that grew fast, and every one of them becomes an exposed API on the other side, with a contract, a version and a failure mode it did not have before.
  • Do consumers read those entities directly in aggregates? Count the call sites, not the consuming modules. A consumer with one aggregate against a public entity and a consumer with thirty are the same row in Discovery and very different conversions.
  • Do its public server actions touch entities? If they do, they cannot go into a library with the blocks that use them. Either the logic moves into the owning app and gets exposed from there, or the block is refactored to raise an event and let the consuming app do the data work. Both are valid. Neither is automatic.

Part 4

Where a module lands

Four answers put each producer in one of five places.

  • Becomes an app. It owns entities, consumers write through its own public actions, and its blocks are either absent or only ever used inside it. Cleanest case. The work is exposing what consumers need and nothing more.
  • Becomes a library. No entities, no actions that touch entities. Pure logic, blocks, structures, theme. Converts close to as-is, and picks up versioning on the way, which is a change to the release model rather than to the code.
  • Splits. Owns entities and ships blocks or public actions that touch them. The entities anchor to an app, the blocks want a library, and the actions that read or write data cannot travel with the blocks. This is the classic core module, and it is the row that needs a person who knows what the module is for, because the split is not one decision, it is one decision per public action.
  • Folds in. Owns one or two entities, has one or two consumers, and no reason to exist on its own any more. Merge it into the app that actually uses it. This is the best outcome for most of the small shared modules from section 2, and it is the one nobody proposes because nobody wants to admit the module never earned its boundary.
  • Retires. No live consumers, or consumers that are themselves being retired. More of these than people expect, and each one is a row that costs nothing.

Part 5

The count that predicts the work

Module count does not predict conversion effort. Application count predicts it even less. The number that does is the total of three things, summed across the sheet.

  • Direct cross-module reads against public entities, counted by call site. Each one is either a new exposed read on the other side or an argument for keeping two things in the same app.
  • Direct cross-module writes. Each one is an API to design.
  • Public actions in split modules that touch entities. Each one is a move-or-refactor decision.

Two estates with fifty applications each can differ on that total by an order of magnitude. Fifty applications on eight well-drawn core modules is a different programme from fifty on forty, and the sheet is the only place that difference is visible before the programme starts.

Part 6

What else goes on the row

A few columns that do not feed the placement decision but feed the plan.

  • Who still owns it. A name, in the building, who can say what the module is for. Blank is a finding.
  • Delete rules that consumers rely on. Relationships between entities in different apps do not create a database constraint on the other side, and the delete rule is always ignore. Any Protect or Delete rule a consumer currently leans on becomes application logic once the relationship crosses an app boundary. List them now. After conversion they are invisible until the orphans show up.
  • Traditional Web screens. Any in the module means a rewrite before a conversion.
  • Anything modelled as a long-running process. BPT has no direct equivalent. Flag it. It gets its own design conversation.
  • Extension references. Which extensions the module calls, and from how many places. The extensions themselves get a separate read, because what happens to them is decided inside the assembly, not in the metamodel. We wrote that up on its own: What happens to your C# when the runtime changes.

Part 7

Then check the sheet against the people

The inventory is what the code says. Roughly half the rows that look like a split are not, once someone who was there explains why the module was shaped that way. An entity that looks shared was a workaround for a deploy order problem that no longer exists. A public action that touches data was meant to be private and never got changed. A module with no owner has one, they just moved teams.

Take the sheet to the team before it goes into anything with a number on it. The read gives you the questions. The people give you the answers. The plan needs both.

Not sure what your producers become?

This is the inventory we build inside a two-week estate assessment, before the compatibility score gets read. If you have an O11 estate and a conversion conversation starting, it is worth having the sheet before the plan.