Skip to main content

Philosophy

Andromeda is built around one idea, strictness. The broker accepts what NGSI-LD v1.9.1 allows and refuses everything else, and the code behind it is held to the same standard.

Why a broker should be strict​

A context broker sits between the systems that write context data and the ones that read it. A lenient broker takes a request with a misspelt parameter, an attribute without a type or a timestamp in the wrong zone, and does something with it anyway. The writer never finds out. The readers get data that means something slightly different from what was intended, and a second broker, lenient in its own way, gives them a different answer to the same question.

Many systems depend on the same broker, so whatever it lets in becomes everyone's problem. Once a bad value is stored, every subscriber, query, federated broker and analytics job sees it, and none of them can repair it. A refusal costs the writer one round trip, and it reaches the one party who can fix the request while they are still looking at it.

The specification decides​

NGSI-LD v1.9.1 is the authority for everything the API does. It outranks convenience and the way other brokers behave. Where it says a request is malformed, Andromeda answers 400 with a detail naming the member and the rule. Where it says an implementation should raise an error, Andromeda reads "should" as "shall" and raises it.

If the specification leaves a choice open, Andromeda makes one, gives the same answer on every endpoint and documents it in the behaviour pages. Some things a standard requires or allows a broker to accept, and Andromeda accepts them. Deliberate tolerances lists those cases. Anything Andromeda adds is an extension, and every extension apart from cursor pagination stays off until you turn it on.

Nothing is repaired on the way in. Andromeda doesn't respell, default or drop anything to make a request fit, and there is no lenient mode to switch on. The only rewrites are canonical forms the specification defines itself, such as a name expanded under the request's @context. Strictness goes through what that means request by request.

The tests follow the same rule. More than 17,000 of them cover the broker, including the ETSI NGSI-LD conformance tests and the examples from annex C of the specification. When a test catches the broker disagreeing with the specification, the test keeps asserting the specification and keeps failing until the broker is fixed. Nobody rewrites it to match whatever the broker happens to do.

The same rule inside​

Strictness at the API only lasts if the code behind it can't drift, so the code gets the same treatment as the requests.

  • Values are types

    An IRI, a URN, a language tag, a Scope, a date-time and every specification token has a type of its own, which can only be made from valid input. A request is decoded straight into those types, so a malformed member fails at that member. Typed model covers this.

  • Errors are types

    Every failure is a typed error, and its message is part of the error rather than something the HTTP layer writes. The broker never blames your request for a fault of its own. Error model covers this.

  • The build has the last word

    Every compiler and lint warning is an error. The workspace forbids unsafe code, denies unwrap, expect and panic! outside tests, and denies todo!, unimplemented!, unreachable!, silent numeric casts and wildcard matches everywhere. A case nobody handled fails the build instead of crashing the broker.

  • One rule, one implementation

    Tests fix which crates each crate may depend on, and source checks fail the build when SQL, untyped JSON or a second reading of a rule turns up in the wrong layer. An endpoint can't judge a request differently from its neighbour, because there is only one place the judgement is made. Boundaries covers this.

  • Old code is deleted

    When an internal interface changes, every caller changes with it in the same change. No compatibility layer keeps an old path alive beside the new one, so there is never a second behaviour waiting behind a flag.

Data stays standard and whole​

Inside, an entity is a set of typed rows, not a JSON document. What goes in and comes out is still plain NGSI-LD, so it can be read back, forwarded to another broker or exported without anyone having to cope with a dialect only one broker produces. Andromeda never stores a standard format in a shape of its own to save effort. When a representation is expensive, Andromeda changes its in-memory model, not the stored format. Rows, not documents tells how the row layout came about.

Each change and the history it produces are written in the same database transaction. There is no background recorder catching up later, so the temporal history never falls behind the current state. In durable mode, the notification a change triggers is queued in that same transaction too.

Few moving parts​

A deployment is the broker and one PostgreSQL server with PostGIS and TimescaleDB. There is no message queue, cache or separate history service beside them. The notification queue and the history live in the database the broker needs anyway, and the broker maintains that database itself. That leaves a single store to back up, and a single place to look when something goes wrong.

The price of strictness​

Strictness has a price, and some of it lands on you. A client written for a more lenient broker gets refusals until it sends what the specification says. Common refusals lists the ones that catch people most often. Inside, a new feature needs its types, its errors and its boundary entries before it builds, and that makes every change slower to write.

Both costs are accepted on purpose. A broker is the one place many systems agree on, and for a broker whose value lies in being right about a detailed specification, a refused request is cheaper than a wrong answer.