Simplicity is complicated. In the Golang community, this statement is most often attributed to Rob Pike but it turns out a lot of people said something like that. A couple of weeks ago I encountered this yet again in a debate with a colleague (someone with lots of experience and whose opinion I respect a lot). Both of us considered ourselves advocates for simplicity, yet we were leaning towards radically different technical approaches. Both, of course, were sure that our own solution is much simpler than the other, and even had a good set of technical arguments to support that.
Without going into too many details, we needed a bunch of small business logic snippets executed ever so often against a certain dataset. The whole thing was supposed to be pretty small and simple (no high-load, out of the critical path, latency insensitive, etc.), so the main concern was to minimize maintenance and debugging effort. In this particular case, the language was Go, but frankly, this can be applied to any language. The two options at the table were:
Define a concrete struct type to hold snippet metadata and snippets themselves would be stand-alone, stateless functions. Execution manager will be another struct that would hold a list of snippets and manage their execution. The execution manager would also hold a bundle of all extra dependencies (e.g. static data, RPC clients, etc.) a snippet function might need and that bundle would be passed over to all snippets as a function argument.
Pros: Everything is explicit and visible at-a-glance: all external dependencies (which should be very few) are in one place, every snippet is a stateless function; minimal amount of indirection. Concrete implementation leaves minimal amount of “creative freedom” for those who will be writing the snippets, discouraging them from using the system for things it wasn’t supposed to be used for.