Day 4: The Purpose and Classification of Definition Files
DEV Community

Day 4: The Purpose and Classification of Definition Files

The Three Boot Files: What This Deployment Runs On

"Definitions are the single source of truth" sounds like there is one file holding all of the truth. Open a project's definition directory and what you actually see is thirteen definition types.

Some are a single file at the root, and some are one file per program, scattered through subdirectories. This post is a map. Every mechanism discussed later uses a few of these, so meeting them here means not having to stop and explain each time.

At the end of the fourth section there is a quick reference covering all thirteen, marked with which post expands each one. If a name is unfamiliar later, come back here.

This post covers:

  • The three boot files: SystemSettings, DatabaseSettings, DbCategorySettings
  • The three files for one form: FormSchema, TableSchema, FormLayout
  • Binding and navigation: ProgramSettings, MenuSettings, PluginSettings
  • The four shared by every form: LanguageResource, PermissionModels, CurrencySettings, UnitSettings
  • Four things all thirteen have in common: storage, caching, immutability, and what happens when one is missing

Each name below links to its source.

Definition Stored as What it governs
SystemSettings A single file Where the master key comes from, how the payload is packed and encrypted, and debug mode
DatabaseSettings A single file Which physical databases this deployment has, and how to connect to each
DbCategorySettings A single file Which category a table belongs to, and which database carries that category

These three are the precondition for the system starting at all, and they are read in a fixed order: SystemSettings (names where the master key is) -> the master key decrypts the key that protects settings -> which decrypts the connection passwords in DatabaseSettings -> only then do the database identifiers in DbCategorySettings resolve to anything.

The order is not a convention. It is the only solution the dependencies allow. The reverse path does not exist: before a database can be reached, there is no opportunity to look a key up in a database.

The first link in the chain has to be readable when nothing else has started, so it cannot be encrypted itself. That is why the master key is not stored inside SystemSettings. That file records only where the key is:

<MasterKeySource>
  <Type> Environment </Type>
  <Value> BEE_MASTER_KEY </Value>
</MasterKeySource>

The framework separates that one startup read accordingly: no service, no cache, straight off the file, because at that moment nothing else exists yet.

Why the Database Concerns Are Two Files

They answer different questions:

DatabaseSettings DbCategorySettings
What it answers Which databases this deployment has and how to connect Which category of data lands in which database
What it is A fact about the deployment environment A fact about the application's structure
When it changes Moving data centers, swapping database servers, repointing a test environment Adding a table or moving a body of data to a different database
How sensitive it is Holds connection passwords and needs encryption and access control Holds no secrets, and developers edit it daily

Splitting them lets each side change on its own, and separates the sensitive from the everyday as a side effect.

How many databases the data actually lands in, and how that relates to the table name prefix, is the subject of Connections, Transactions and Query Conditions in the Data Access Layer.

The Definition Layer Holds Database Settings Without Knowing Databases Exist

A file recording connection strings, filed under the definition layer, looks like a layering violation. It is not, because what it holds is a description, not a connection.

The definition layer package has an extremely short reference list. It does not know a database exists, or HTTP, or a UI. A connection string, as far as it is concerned, is a piece of text with an identifier attached.

What actually opens a connection is the data access layer. The definition layer only declares an interface that can hand over DatabaseSettings, and lets the data access layer use it.

The Three Definitions of One Form

Definition Stored as What it governs
FormSchema One per program The hub: fields, types, cross table relationships, master and detail, computed fields and validation rules
TableSchema One per table The physical table's columns, types, lengths, nullability and indexes
FormLayout One per layout How those fields are arranged on screen

The three edits for adding a required date to orders in Why ERP Needs a Definition-Driven Framework are these three files. FormSchema is the hub. The other two each face one direction, one toward the database and one toward the screen.

FormSchema declares which fields exist, what type each is, which ones point at other forms, which details hang off the master, which values are calculated, and what to check before saving. The framework uses it to assemble CRUD SQL and to decide the shape of data crossing layers.

TableSchema cares about physical storage rather than business semantics, so lengths, indexes and precision can be tuned separately by whoever knows the database. FormLayout is the only one of the three that concerns the screen alone.

How things are arranged is a design time decision, written into a layout definition and followed at run time.

The relationship between the three also fixes the order of edits. FormSchema describes which fields the form has, so a change starts there. TableSchema follows with the physical column. If it needs to be visible, it then goes into FormLayout.

Binding and Navigation: Who Handles This Program, and Where It Appears

Definition Stored as What it governs
ProgramSettings A single file Which business object and which repository a program identifier binds to. Left blank, the framework default applies
MenuSettings A single file The navigation tree's grouping, ordering, captions and visibility, with each item pointing at a program
PluginSettings A single file Which plugins run at save and delete for each program, executed in declaration order

ProgramSettings is a registry, one row per program, deciding who handles that program's requests. Leave the two binding columns blank and the framework's default CRUD flow applies. Name a class and that class takes over.

When The Northwind Case Study: Eight Forms and a Custom Business Object said that of the eight forms only the order needs code, this registry is what it was reading.

MenuSettings is separate from the registry because the two are read by different ends. The registry is read by the server and holds class names from the code. The menu is read by the front end and holds ordering, captions and visibility.

On top of that, the same program can legitimately appear at several places in the menu, so a node carries its own identifier rather than using the program identifier as its key.

PluginSettings records which plugins run before and after save and delete for each program, and in what order. The difference from inheritance and overriding came up in an earlier post: inheritance replaces and there can be only one, while plugins layer and there can be several. The details get their own post later.

The Ones Every Form Reaches For

Definition Stored as What it governs
LanguageResource One set per language, split by form or category Localized captions and option text
PermissionModels A single file Which permission models exist, which actions each governs, and how data scope is delimited
CurrencySettings A single file The decimal places and smallest circulating denomination of each currency
UnitSettings A single file The display decimal places of each unit of measure

These four belong to no particular form, and every form reaches for them.

LanguageResource is applied one entry at a time. FormSchema already carries field captions; the language resource supplies per language versions. Where one matches it replaces the caption, and where none matches the original stands.

A project that does not need multiple languages never has to touch it, and adding it does not force you to translate every entry.

PermissionModels registers what permissions there are to talk about. Which actions require authorization and how data scope is delimited are models. Who has been granted what is data rather than definition, and does not live in this file. A later post takes that apart.

Currency and unit of measure are system level: how many decimal places a currency has, how many a unit displays, consistently across the system. Which currencies a given company may use, and what its cash rounding is, are company level settings that come from the database with the company's data rather than from a definition file.

Both levels have their place, and a later post on numeric semantics uses them.

The Thirteen at a Glance

The four sections above group by purpose. Here all thirteen are laid out together in the same order:

Definition What it governs Where it is expanded
SystemSettings Master key source, payload options, debug mode This post
DatabaseSettings Which databases this deployment has and how each connects This post
DbCategorySettings Which category of data lands in which database This post
FormSchema The hub, being fields, types, relationships, master and detail, rules and computed fields The next post
TableSchema The physical table, being columns, lengths, nullability and indexes TableSchema and Database Structure Upgrades
FormLayout How this form is arranged on screen FormLayout and Screen Generation for Multiple Front Ends
ProgramSettings Which business object and repository a program binds to. Server side only API Contract Layering and the Type Registry
MenuSettings The navigation menu's grouping, ordering, captions and visibility API Contract Layering and the Type Registry
PluginSettings Which plugins hang off each program, in declaration order Business Logic Customization and the Four Plugin Stages
LanguageResource Captions and option text, one set per language Multilingual Customization and Resource Structure
PermissionModels What permissions there are to talk about, being actions and data scope policy The Three Axes of Permissions: Action, Row and Column
CurrencySettings Decimal places and smallest circulating denomination per currency Numeric Semantics, Multiple Currencies and Units of Measure
UnitSettings Display decimal places per unit of measure Numeric Semantics, Multiple Currencies and Units of Measure

These names recur over the next twenty odd posts, always in their original form. A descriptive phrase cannot identify which one is meant. "The category file" could be DbCategorySettings or it could be PermissionModels, and "the settings file" fits eight of the thirteen.

Four Things All Thirteen Have in Common

They Are All XML, and Where They Are Stored Is a Separate Question

The content is XML. Where it lives can be swapped: a file on disk, or a row in a database table with the XML in a column. The framework reads through one interface, so changing the storage means no layer above has to know.

What gets used at run time is not that XML. The framework reads it and restores it into a definition object, and everything after that is a question asked of the object. The XML is only what it looks like while lying on a disk or in a database.

Once Read, They Stay in Memory

What sits in the cache is those restored objects, one slot per definition, evicted by usage. Reading one resets its timer, and only a definition nobody has touched for a while is dropped.

  • The forms in daily use keep their definitions in memory almost permanently
  • The one opened twice a year is evicted not long after
  • The server does not carry the definitions of forms nobody opens

This is one of the returns on cutting definitions into separate pieces. The unit of caching is the unit of reading, so memory tracks actual usage rather than the total volume of definitions.

Keeping them does create one obligation: what happens when the source changes. Each cached definition watches its own source. A file is watched by modification time, and a database goes through a different notification mechanism. When the source changes, the next use reads a fresh copy. Cross-Node Invalidation for Definition and Data Caches takes this apart.

A Definition You Were Handed Must Not Be Modified

Asking the framework for a definition gets you the shared one, not a copy. On the same server, every user gets the same object. The price is that the object cannot be touched from then on:

  • Want a view for one user only, with captions in their language? Copy first, then modify.
  • Want to persist a change? Go through the framework's save method, which writes the file and invalidates the cache.

The only thing forbidden is modifying the shared copy in place, and that was always wrong anyway, since what it modifies is everyone's.

Missing Comes in Two Kinds

The framework embeds a set of default definitions, and at run time they take no part at all. The framework reads only the definition directory you point it at. The embedded set is a starting point for a new project, copied in with a tool, once.

Not falling back is deliberate. If a missing FormSchema silently switched to the embedded one, the system would start up fine and then run on a definition you never deployed. "You forgot to deploy that file" would be rewritten into "the system does not behave the way you think," and the second one costs an order of magnitude more to track down.

But missing does not mean only one thing, so all thirteen are sorted into two kinds:

Kind Behavior Which ones
Missing means misconfigured Report it immediately with a message naming the file FormSchema, TableSchema, ProgramSettings, PermissionModels and the three boot files
Missing is a legitimate answer Return nothing and let the caller take it from there FormLayout, LanguageResource where the caption in the structure is used, CurrencySettings and UnitSettings where default decimal places apply, MenuSettings and PluginSetting
Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.