My team writes our code in a similar style. We have many components that do as few things as possible as well as possible, outsourcing policy decisions through callback modules. We then compose these components together to build out new functionality.
For example, we have a framework which handles invisibly performing an action that might transiently fail and retrying it later. Has a simple API: start, call, and stop. It takes 2 callbacks: the work to do on a call and how to queue the data.
The queue interface is built around and component we have which represents queues or streams. This has a simple API: start, publish, subscribe, ack, nack, stop. And start take a callback implements the various underlying API calls. We have callbacks that represent RabbitMQ connections, on disk queues, in memory queues, etc. This queue component is completely distinct from the store and forward component.
So someone can easily build out work processors that will retry later with varying degrees of persistence.
By stacking these things we’ve easily created multiple tools that do work and then publish it to an MQ, with a layer of durability between the work and the publish incase the connection fails.
What’s cool is that in building tools like this sometimes you are given a new problem and realize that by composing together a few components you have written in a way you didn’t think of previously, you can solve that new problem quickly and feel confident that it will work.
This style has a lot of upsides. By pushing policy decisions to callbacks, testing doesn’t involve mocking anything, just create fake policies that does what you want to test. Understanding components becomes easier because there is a clear distinction between what the component does from the business logic portions. Debugging has been surprisingly easy as well. We almost never need to make use of stracktraces, the error is almost at the leaf and obvious by reading the error + the module the error happened in. Things do so little, the error alone is often sufficient to debug what’s going on.
Sometimes new people that read our code are confused for a bit. For the most part they seem to get it after awhile and some even find it enjoyable to contribute in. But it’s not for everyone.
My team writes our code in a similar style. We have many components that do as few things as possible as well as possible, outsourcing policy decisions through callback modules. We then compose these components together to build out new functionality.
For example, we have a framework which handles invisibly performing an action that might transiently fail and retrying it later. Has a simple API:
start,call, andstop. It takes 2 callbacks: the work to do on acalland how to queue the data.The
queueinterface is built around and component we have which represents queues or streams. This has a simple API:start,publish,subscribe,ack,nack,stop. Andstarttake a callback implements the various underlying API calls. We have callbacks that represent RabbitMQ connections, on disk queues, in memory queues, etc. This queue component is completely distinct from the store and forward component.So someone can easily build out work processors that will retry later with varying degrees of persistence.
By stacking these things we’ve easily created multiple tools that do work and then publish it to an MQ, with a layer of durability between the work and the publish incase the connection fails.
What’s cool is that in building tools like this sometimes you are given a new problem and realize that by composing together a few components you have written in a way you didn’t think of previously, you can solve that new problem quickly and feel confident that it will work.
This style has a lot of upsides. By pushing policy decisions to callbacks, testing doesn’t involve mocking anything, just create fake policies that does what you want to test. Understanding components becomes easier because there is a clear distinction between what the component does from the business logic portions. Debugging has been surprisingly easy as well. We almost never need to make use of stracktraces, the error is almost at the leaf and obvious by reading the error + the module the error happened in. Things do so little, the error alone is often sufficient to debug what’s going on.
Sometimes new people that read our code are confused for a bit. For the most part they seem to get it after awhile and some even find it enjoyable to contribute in. But it’s not for everyone.