Photo by Masaaki Komori on Unsplash (Edited)

Shutting the gate on entity updates

Extending a “locking” primitive to allow updates on given primitives in ${N} directions

Recently I had the chance to work with the at Global Fashion Group in Berlin to help them work on shared services across ventures such as the Iconic, Zalora, Dafiti and Lamoda. This business operates at several orders of magnitude more scale and has grown aggressively in its fairly short time to become among the the largest fashion companies in the world.

Scaling Further

One of the primary ways in which we can ensure a responsive service is by identifying types of work that does not need to be accomplished immediately (i.e. within ~1s) but can instead be accomplished “in the background”. This sorts of work may be reconciliation, complex validation, coordination with other services and so fourth.

Queues (RabbitMQ)

Broadly, a queue that receives some notification of change or specific command to execute and does the work when it reaches the end of the queue. It is typically extremely cheap to write to the queue and the queue can be configured to process work at any given rate.

Distributed Commit Log (Kafka)

A commit log is conceptually similar to a pub/sub queue but items are never really “consumed” from the queue. Rather, the commit log simply tracks the command or state change and leaves the vast majority of the work to the consumer.

Reconciliation (Control Loop)

Comfortably my favourite way of shifting work to the background is via a reconciliation loop.

Opening closed gates

// A person entity
message Person {
string name = 1;
int32 id = 2; // Unique ID number for this person.
string email = 3;
}
// A gate entity
message Gate {
// The type of change that needs to be completed before the person is "good to go"
string name = 1;
// The transaction in which the gate was created
string txn_id = 2;
}
// A person entity
message Person {
string name = 1;
int32 id = 2;
string email = 3;
// What blocks the user
repeated Gate gates = 4;
}
{
"name": "Andrew Howden",
"id": "c78a033c-a7ae-11e9-a71e-e7787f36f4f1",
"email": "my-email@gfgtech.com",
"gates": [
{
"name": "IsVentureMember",
"tx-id": "f1fe8db8-a7ae-11e9-9620-7beb7294ae9b"
},
{
"name": "IsEmailApproved",
"tx-id": "f1fe8db8-a7ae-11e9-9620-7beb7294ae9b"
}
]
}
if yes := user.hasGate("IsEmailApproved")); yes == true {
return fmt.Errorf("user '%s' cannot log in, is gated by '%s")
}
// Path is in `jq` syntax.
[
{ "op": "remove", "path": ".gates | select(name == IsEmailApproved)" }
]
[
{ "op": "remove", "path": ".gates | select(name == IsEmailApproved)" },
{ "op": "add", "path": ".gates[]", "value": { "name": "UserInvalid", "txn_id": "04084006-a7b6-11e9-82c4-130b6d87b05d" }}
]

Gate Limitations

That said and done there are some limitations to using the “gate” primitive to track state changes in a system.

Indefinite

Unlike queues or a commit log which has some notion of expiry, a gate generally does not. This means that if a system creates a gate and then later discards that type of gate, there is nothing there to “clean up” that gate.

Undocumented API

Unless careful, gates can become an undocumented API. While third party systems should be allowed to read and write gates, systems should not depend on gates maintained by a third party unless the API maintains the gate in some official documentation or specification.

Unusual

To my knowledge, the gate primitive has never before been expressed in this. It is similar to a lock, but multi dimensional. Accordingly it may take some time to get used to.

Gating all the things

Gates allow the opportunity for extensibility fairly easily as they’re essentially a dirty indication with direction.

Cheap

Gates make for cheap writes. Gates should be extremely quick to read and write to, and they balance the idea that there is “processing” happening on a given entity, but that the processing is not yet complete.

Extensible

Gates can be extended over time to include an almost arbitrary number of different processes. Given entities that are complex and might require several layers of validation or transformation (for example, a product) a gate allows each process to maintain its own gate, and for those processes to be executed in parallel.

Reapplicable

Gates can go from being “not applied” to being “applied” as a result of other system events.

Idempotent

Gates are idempotent. They can be applied or deleted in sequence, and there is no harm to “reapplying” an existing gate.

In Conclusion

After joining the new company I have found a set of new, and interesting challenges. One of the challenges is helping our partners experience a quick and easy system such that their work is much easier, and to shift as much of the “heavy lifting” work into the background away from user interactions.