skip to content

A metadata type is itself a declaration, so it can be marked. What does a toolkit gain by marking its own markers?

level: seniorimportance: nice to knowfreq 26%

answer

  1. markers are declarations too
  2. a target list can name that kind
  3. the rules are themselves markers
  4. composition instead of a supertype
  5. readers must look one level up

basics

~20 s

Two things: the rules of a metadata type - its permitted targets, its repeatability - are expressed in the same mechanism rather than separate syntax, and several markers can be grouped into a recognisable family by carrying one shared marker, which metadata types cannot get by extending each other.

solid answer

~40 s

A metadata type is an ordinary declaration, so a metadata type whose target list includes the metadata-type kind can be attached to it. That is how a system usually expresses the declaration rules themselves: which sites a marker permits and whether it repeats are stated by marking the marker. The same mechanism gives composition. Since metadata types cannot extend one another, a toolkit that wants "these five markers are all rate-limit policies" marks all five with one shared marker; a reader that finds an attached marker can then ask whether that marker is itself marked, and treat the whole family alike without knowing five names. The cost is a level of indirection: a reader that inspects only directly attached markers sees nothing of the family, and that failure is silent.

code

pseudocode · 14 lines
pseudocode
metadata type RateLimitPolicy          // the shared family marker
    targets: metadataType

mark RateLimitPolicy
metadata type PerUserLimit
    targets: method
    member permitsPerSecond: number

mark PerUserLimit(permitsPerSecond = 10)
function chargeCard(order)
    ...

// reader asking "is the attached marker itself marked RateLimitPolicy?" -> yes
// reader inspecting only direct attachments of RateLimitPolicy       -> nothing

go deeper

for a junior

Know the shape only: a metadata type is a declaration like any other, so metadata can be attached to it, and that is how a marker's own rules are usually stated.

for a middle

Explain composition - several markers carrying one shared marker form a family a reader can recognise with a single lookup one level up.

for a senior

Show that you know what the indirection costs: readers built for direct attachments see nothing, the walk needs a depth rule, and the shared marker becomes a contract other teams attach themselves to.

for a principal

Decide whether the toolkit gets a composition vocabulary at all, since every reader in the platform then has to implement the same walk with the same depth rule.

## A metadata type is a declaration, so it can be marked There is nothing special about the declaration of a metadata type. It has a name, it has members, and it lives in a file like everything else - which means it is itself a declaration kind that a target list can name. A metadata type whose target list includes **metadata-type declarations** may be attached to other metadata types. That is all "meta-metadata" means, and once you see it the design of most metadata systems becomes much less mysterious. ## What marking a marker is used for - **Stating the marker's own rules.** The target list itself is typically expressed this way: a built-in marker meaning "this metadata type may be attached to these kinds" is attached to your metadata type. Repeatability is usually declared the same way. The system needed no new syntax for rules about markers, because markers already were the mechanism for attaching rules to declarations. - **Composing a family.** Since one metadata type cannot extend another, the only way to say "these belong together" is to mark them all with one shared marker. - **Documenting intent for humans and tools.** A marker meaning "internal toolkit surface, not for application code" attached to a set of markers is readable by a reviewer and by a check. ## Composition is what inheritance cannot give you A toolkit with a per-user limiter, a per-tenant limiter and a per-endpoint limiter has three metadata types with different members. They are not variations of one type in any sense the system can express - there is no supertype for them to share. What they can share is an attachment: each of the three carries one `RateLimitPolicy` marker. The reading side then works one level up: | What the reader inspects | What it finds | |---|---| | The markers directly attached to the operation | the specific limiter, by its own name | | Whether each of those markers is itself marked | that it is a rate-limit policy at all | That second question is the one that makes the family real. A reader that asks it needs to know exactly one name - the shared one - and keeps working when a fourth limiter is added next quarter. A reader that does not ask it needs the list of three names, and will be wrong the day the list grows. ## What it costs 1. **Indirection that is easy to miss.** A reader written to inspect direct attachments only will never see the shared marker. Nothing errors; the family simply does not exist for that reader, which is the branch's usual failure shape - something worked, something silently did nothing. 2. **Depth questions.** If a shared marker is itself marked, how far up does a reader walk? One level is predictable and cheap; arbitrary depth needs a cycle guard and a written rule. 3. **A public surface that is larger than it looks.** The shared marker is part of the contract: teams will start attaching it to their own markers, and whatever your readers do with the family, they will get. 4. **Harder navigation.** The reason a piece of code behaves a certain way is now two declarations away from the code, and neither of them is in the file being read. ## When to reach for it Compose when the family is genuinely open - when new members of it are expected, and every reader should treat them alike. Do not compose to save typing on a closed set of two; naming both is clearer and costs a reader nothing. And when you do compose, make the shared marker's meaning narrow and stable: it is the one name every reader in the toolkit will hard-code, and changing what it means changes the behaviour of every marker that carries it, in code none of those teams will touch.

  • How deep should a reader walk when markers carry markers?
    Pick one rule and write it down. One level is predictable, cheap and enough for most families. Arbitrary depth is occasionally useful but needs a visited set, because a marker can carry a marker that carries it back, and the walk will not terminate on its own.
  • Why not just give the shared members to one marker and let the three limiters reuse it?
    Because there is no reuse relationship to use - metadata types generally do not extend one another, so members cannot be factored upward. The shared marker carries no members for the others; it only makes them recognisable as a family. Shared members have to be repeated in each declaration.

saying these in an interview costs you the question

  • Thinks a metadata type cannot itself carry metadata
  • Believes a composed marker is seen by readers inspecting direct attachments
  • Says marking a marker changes what it does at the attachment site
  • Confuses composing a family with a metadata type inheriting members
  • Assumes a reader walks the marker graph to unlimited depth for free