You should not have a giant, monolithic application. Monolithic codebases are the mind-killers. They are the little-deaths that bring total obliteration.

Face your monolith. Allow it to pass over you and through you...

... into a well-modularized codebase.

But here's the corollary nobody links to this practice: 

The best modularization requires smart denormalization and repetition. 

You catch some of this when you hear about microservices, which hold down the other end of the spectrum from monoliths. I think it's largely because of their association with NoSQL, where being comfortable with denormalizing schemas is absolutely essential. 

I don't want to get too deep into NoSQL right now, but the concept is the same: To be most nimble in a modularized codebase requires that you -- and it's nearly against my programming north star to admit it -- Repeat Yourself.

And that's okay. If you have one service that calls another, and you need to massage your payload, you may have to change it two places, the caller and the server.

THIS IS FINE, NO MEME-MIC DOG & FIRE SHOW REQUIRED.

Because you know what's worse than changing a model two places? Changing it three places.

  1. The shared library
  2. Updating the reference for the caller
  3. Updating the reference for the server

Those last two are too often nontrivial. Perhaps they should be trivial, but in practice they almost never are. There's always some argument that 1. requires a breaking change to the shared library, which requires we up its major version, and 2. & 3. are set to update automatically on minor changes, but not major changes, and now we have to redeploy in just the right order so that the other apps that depend on the library in 1. aren't brought down when we... I see you over there. You get it. This has happened to you too.

What were we hoping to save ourselves from again? Exactly this sort of tedium. Except now it's even worse than it was before in the monolith!

Look, you should be coding the caller and server defensively anyhow, as independent services. The payloads for both, once serialized, are probably coming across the wire as XML or JSON (please JSON) anyhow, so it's not like these model abstractions are necessary beasts. Why do we have to point them both to the same library to have a source of truth? We're comfortable consuming third party apps without sharing model definitions, and that's [at least partially] the setup you chose when you went to modular services.

Treat each app as an independent development and change the model twice -- or, when appropriate, add a new model to one with a new API endpoint to maintain backwards compatibility with other consumers. Denormalization between apps at the point of the interface is a-ok. 

I'm requesting foo. You're sending foo. Having two copies of foo, one for me and one for you, is fine. Even having a slightly different foo for each is often fine as long as the middle of the Venn is what the transaction in question is worried about.

No, for real. It's okay. Really. You'll thank me later.

Labels: , ,