Thought Experiment: Creating Single Use-Case Components In ColdFusion
As I've written about in the past, my ColdFusion applications tend to have tiered layering with the "Workflow layer being the outer-most layer of the "application core" (ie, what the "Controller layer" is consuming). This layer is composed of ColdFusion components that are organized around tightly-coupled features. For example, on this blog, I have a
CommentWorkflow.cfc component that has methods for adding, editing, and approving comments. At work, these workflow components can get pretty beasty, especially as a feature-area continues to expand. As a thought experiment, I wanted to consider a world in which each workflow component represented a single use-case within the application.
To be fair, I'm not pulling this concept out of thin air. Twelve years ago, in the presentation: Ruby Midwest 2011 - Keynote: Architecture the Lost Years, Robert "Uncle" Bob talks about creating "Use Cases" to describe what an application does. And, uses this set of objects much in the same way that I use my course-grained "Workflow" layer.
Then, a few years after that, I discussed a thought experiment inspired by Sandi Metz in which all private methods should be moved into other components as public methods.
Essentially, what I am thinking about now is the combination of the two aforementioned concepts: Can I create "use case" workflow components that only have a single public method; and, remove duplication by moving all/most of the private methods into other cohesive components so that they can be reused across the various use-cases?
Let's make this more concrete. In my
CommentWorkflow.cfc, I have several public methods that revolve around comment moderation:
Each of these public methods turns around and calls one of these private methods:
In his presentation on the Rules of Simple Design, Corey Haines talks about "reification": the process in which an idea, poorly expressed in software, is factored out into its own first-class entity with well defined behaviors. Essentially, this gives a "name" to the idea that Sandi Metz was describing when she talked about moving private methods into other classes: those new classes become a first class "Thing" / "Concept" within the system.
In this case, we can take the two methods around signature generation and move them into their own ColdFusion component. Since naming things is one of the "hard parts" of computer science, I won't dwell on it - for the sake of the thought experiment let's just call this new component:
... which has two methods:
Once this idea of comment moderation signing has been factored-out into its own concept component, we can then go back to the
CommentWorkflow.cfc component, grab those four methods mentioned above, and move them each into their own respective ColdFusion components:
CommentModerationSigner.cfc component (along with several other components) would then have to be provided to these four new components via some form of Inversion of Control (IoC), such as a Dependency Injection (DI) framework.
Each of these new components would then have some sort of public method that is the "do the thing" method. Something like,
.doTheThing(). I don't know - naming stuff is hard!
What I Don't Like About This Approach
There are several things about this approach that are immediately distasteful:
There will be an explosion of components. While each component is now laser focused, there is an emotional and a cognitive weight to having more files. It's just more to keep track of.
Previously collocated features are no longer (necessarily) "next to each other". Look at the "approve" and "reject" methods. They used to be very close to each other visually. But now, they are components whose respective filenames start with "A" and "R". These files may be separated by many other files in the file-system.
Though, I suppose you could start grouping "use case" components into their own cohesive folders?
The amount of "wiring" increases dramatically. When several use cases were in a single file, a single set of injected components could be reused. But now, with each use case in its own component, every use case needs to have its own set of injection dependencies.
More "names" have to be created. This is not to be taken lightly - naming stuff is really hard. And, once we start elevating concepts into their own components, we have to come up with a "Good Name" for all those things. Plus, if your IoC/DI mechanics required globally unique name tokens, this becomes all the more challenging.
What I Do Like About This Approach
Despite the immediate anxiety that I feel when thinking about this approach, there are some aspects to it that are attractive:
I do appreciate that it forces me to factor-out reusable functionality. Even though it adds some complexity, I do think it is almost always a good thing and will make future changes / features easier to add.
I do think it will actually help me to collocate some business logic (such as security / authorization) that was previously distributed among many other components.
I do kind of like the idea of looking at a directory and seeing one big list of every action that the system can perform (one action per ColdFusion component file). Even when related files may no longer be right next to each other, having well named components probably makes up for that.
It alleviates the burden of having to decide where a new feature goes. Meaning, not all functionality is solely related to one type of data. And, in my existing architecture, it's sometimes hard to figure out which Workflow component should receive a new method. When every "command" becomes its own "use case" ColdFusion component, I no longer have to choose a parent concept.
What I'll Probably End Up Doing
This thought experiment didn't just materialize out of nowhere - there is some underlying pain that my brain is trying to resolve. But, I don't think I'm ready to take on a big shift in how I organize my code. Instead, what I'll probably do is focus on reducing private methods, ala Sandi Metz. This will, at the very least, force me to package-up reusable functionality without having to massively refactor my components.
So, going back to the concrete example above, I might still factor out the methods relating to "comment moderation signing"; but, still keep all the high-level use-case methods in the single workflow component. Baby steps.
Curious to know if anyone uses an approach like this, where use cases are finely broken up into individual files?
For the sake of convenience, I wanted to embed the videos that I mentioned above:
I'm interested to see where you go with this. It reminds me of the Cruddy by Design concept for the controller layer of an MVC app:
I find this approach to controllers quite useful (and it helps a bit with your naming problems).
I find Sandi Metz's books and talks to be quite helpful, though it seems she's disappeared from the scene recently?
I have a lot of YouTube to catch up on now. Thanks guys! I just watched "Cruddy by Design" and that made a lot of sense. I've recently seen this tactic used in the creation/destruction of auth sessions and I really like that approach.
I am not sure I've seen that one, but I really like Adam Wathan's approach to things (that said, I haven't tried Tailwind).
These conversations are always fun! Never know what will come out of it.
Just watched the Cruddy By Design talk - really good 💪
Post A Comment — ❤️ I'd Love To Hear From You! ❤️
Post a Comment →