I’m very passionate about good coding practices, and even though that’s the case I’ll probably be one of the first to admit that I don’t always get it right. I do work hard to at least try to do right by my colleagues and associates, and I’m always mindful of how my decisions now are going to effect others in the future. Hell, even how it will effect me later on. I’m sure most of us have taken a look back on some of our older code and wondered what the heck we were thinking at the time.
I could reel off a long list of things I feel can help lead to a well kept house, but I won’t, I’m going to save them for future tips in the series. Today I’m going to comment on how it can not only be helpful, but perhaps even downright integral, to think about the steps your code is taking in atomic units of action and not to intermingle them throughout the procedure.
Of course, this statement is easy enough to follow if you’re following SOLID design principles. But it’s useful enough that I think it’s worth calling out on it’s own: think about packaging a unit of functionality up in to a single logical action and keep it all in one place.
Case in point, a recent task was to repair an ailing process built with the intention of keeping data synchronized across two databases. There’s something to be said for only having one source of truth for any given system, but the immediate need did not extend to rebuilding the entire architecture, so fixing the sync was important to ensure we could continue to operate effectively. The system which sourced the data executed the synchronization in real time by emitting a message across an Azure Service Bus queue. This seemed to be somewhat unreliable, with messages of one kind getting through, others not executing fully, and others still not actually making it in to the queue at all. All of these problems translated as frustrated backlog items from the client support team.
One of the immediate problems I saw was that the messaging was not being treated as a single atomic action. At any point in time, the data at the source had to be considered the source of truth, which meant the data on the other side merely needed to reflect that. That essentially meant that whenever a database operation (or series of database operations) were complete, a single event had to happen whereby all changed data was sent out to the queue to be picked up on the other side.
I refactored the existing message entities, which looked more like database data types with nested entities, and made a flatter structure. This meant that data associated with another entity could be updated independently without having to populate and sync the parent. Entities would be told in the source system what they were expected to do, add a new record, update an existing, etc. Now I had a single message type, handled by one queue, that could synchronize a variety of data types together or independently of each other.
In addition, I brought all the code that populated these entities in to one place in each method or class. Whereas before the synchronization type was created upfront and each property populated in line with changes to the source system entities, my new design took those entities, each fully populated by the recent commit to the database, and used mappers to transform them to the sync objects.
The nice thing about this is that we don’t care what the user actually changed. Every time there is a change, just convert all of the data to a sync type, send it over the wire, and let the receiver on the other end deal with it.
So, if you find yourself writing code that mixed actions across multiple entities at a time, consider thinking about the atomic actions in your code. You should be able to write comments along the lines of // Do X to Y, // Do A to B, and have several, perhaps many, actions between each. Even better, just follow SOLID and DRY design principles and save yourself the trouble!