As a developer one of the first concepts you will be introduced to is DRY (Don’t Repeat Yourself) – if logic is re-used around your codebase it often makes sense to bring it into a central place to be standardised and easily maintained. Later on in your career you might learn the hard way that there is value to be had in duplication and redundancy for the right reasons.
At Songkick we spent some time learning the hard way, so now the value of centralising common logic versus promoting weak coupling is something we actively explore and re-evaluate in our architectural decisions.
Tracking an artist versus tracking an event – be DRY on concepts, not code!
On the face of it, the concept of tracking an artist might look similar to tracking an event. For this reason we originally leveraged the same table and used a polymorphic association.
Looking closer and actually these two things are conceptually quite different. Tracking an event implies attendance on a specific date, and has a concept of “interested in/might go”. There is no equivalent granularity for tracking an artist (though maybe there should be a “I would consider seeing them under the right circumstances” option, look out for that in the future!).
When we split out our domain into services some years later we had to run migrations on this table to split out our attendance data from our artist tracking data, and similarly, separating logic out is much harder than combining it. Code-wise, the complexity added to handle both tracking concepts as the use cases evolved outweighed any benefit of the early abstraction.
See further reading for advice on avoiding early “optimisations” (such as abstractions).
Duplication of client models – DRY not necessarily suitable when weak coupling is required
Our front ends implement their own client models when reading data from a service, rather than making use of a client library.
Fewer dependencies gives us ease of deployment
A client library that provided standard client models may well result in less code duplication but it creates a coupling between client and service. Upgrading the client library would require a new deployment of each frontend, even if only a single frontend benefitted from the change – with our approach, we can deploy our frontends without this dependency.
If you’ve read any of our other blog posts, you’ll know we don’t like restrictions (or directives) on when we deploy!
Easier to reason about
Each frontend uses only the resources it needs and we can track our data dependencies to a service endpoint easily without the client library abstraction sitting in the middle.
Duplication of components – DRY benefits can be negligible with fast rates of change
We unashamedly copy code from one front-end html component to another, making each one self-contained and with no dependency on other components. Front-end components are changed and iterated on quickly, and we usually want changes to affect a single component on a page. Any shared components are mapped out by the designer and PM so we know they will change together – if requirements differ we create a new component.
Because we make our components as dumb and atomic as possible, copying is low-cost and low-risk, and we can avoid complicated branching logic.
Optimising for code re-use might not be the right approach – consider that use cases might change, unnecessary dependencies can be created and that at the line level, code can become unreadable with an overly DRY mindset. Be DRY, but not too DRY.