April 27, 2014 1 Comment
Early in my career, I focused on modeling the business nouns– Customer, Account, Product, etc. My designs sorely underestimated the importance of the business processes– Place Order, Open Account, Bill Customer.
My first official OO class went something like this: read through the requirements, circle the nouns, and underline the verbs. The nouns are the objects, and the verbs are the methods. So easy, and so wrong.
I actually heard Jeffrey Palermo criticize this same oversimplification at a user group presentation. I wanted to jump up and shout, “Yes!!! We had the same class!” I think many of us took that same class and received this false gospel.
This shortsighted yet traditional OO design method focuses on a deep analysis of how all the data in the system is related, which is somewhat wasted effort as everything will eventually connect to everything else without a context to establish boundaries of relevance. The resulting model is large and elaborate, chocked full of has-a’s and is-a’s. My biggest OO mistake was designing such a structure, putting in a lot of automatic behavior, and sharing the same model from the GUI to the data access! Yikes. It was and is a brittle, monolithic structure.
Crystallizing all the relationships and behaviors into one large model says a lot about the business, almost always more than intended or realized. It introduces implications that business analysts would say are either inaccurate or unnecessary.
When the product owners request a change that fundamentally shifts some of the relationships– even unimportant relationships they didn’t even know were modeled– it can easily fracture the model in a way difficult to resolve…or explain to the product owners.
I want less implication, fewer connections, less obstruction to change.
I’m a big fan of fixing a problem at its root cause. I hate downstream patches and hacks, the shortcuts; they always lead to further headaches. I’ve grumbled to a team member or two, “In making the harder fix easier, we made all the easy things hard.”
The root problem here is that the model encompasses far too much. It establishes too many relationships and connects too much behavior. Everything was tied together…”for completeness.” In my mind, the tendency for me to do this is closely related to the tendency to gold-plate software and to build frameworks. It’s a good ol’ fashion power trip. I think, “I’ll build this super-powerful framework or API that can do everything!” This can sound like, “Then, whenever I need to do anything for an [x], all I need to do is get an instance and it’s all right there!” The power! The POWER!!! The naivety.
We don’t need one system-wide model, one framework, one all-seeing construct to rule them all, all the system behavior, that is. We don’t even want one model to encompass all the behavior of a customer, or an employee, or an order. We only want enough for what’s happening at the moment. Notice that I’ve transitioned into an action perspective: system behavior; what’s happening; what’s changing. First, define the action.
Take, for example, the process of billing a customer. Traditionally, it sounds like a method begging to be put in a Customer class: Customer.Bill( order ). The first problem I’ve seen here is that I don’t need a whole customer object to bill them– probably just a customer ID and an order ID. What all else went into creating the customer class was wasted. The second problem is that all the other customer capabilities are noise. Customer.UpdateAddress(), .PlaceOrder(), .IsPreferred(), etc. The thing is, nobody needs all that at once.
Instead, imagine an interface (I)BillCustomer that contains an Execute() method. It’s responsibility becomes immediately clear. A potential consumer knows exactly what to expect. The BillCustomer implementation can have very specific dependencies, namely, the actions or steps in the customer billing process. Drill downward in this manner until you hit infrastructure (database, I/O, external systems, etc.).
There are many reasons I like such modeling. For one, it liberates grouping. Before, all customer-oriented processes gravitated toward the Customer class black hole. But now, I can group all those processes however and wherever I want. They can be in the same namespace, or different namespaces; the same assembly, or different assemblies; the same service, or different services.
But generally, I like that process-modeling is process-oriented. TDD and especially BDD have driven my development in such a way that I want to address fundamental system requirements, and those requirements always come down to doing something. Users are never content with a graph of their system, never impressed with elaborate UML diagrams. They expect the computer to do something– to act.
Processing is the fundamental purpose of a computer. I remember back in college a whole class chanting repeatedly with our professor, “Input, Output, Processing, Storage!” Data goes in, data comes out. Often, it’s stored. But the magic is the processing.
Focusing first on individual and independent processes and just the data needed to perform each one separately seems simplistic. I used to turn up my nose at such a simpleton’s idea. But this simplicity, this directness is what a business analyst actually expects is conveyed during requirements gathering. Stick to this simplicity, and requirements will be more directly represented in the system.
Behavior should be a first class citizen in the system, and as such a behavior can be a class. Bill Customer can be a class. Place Order can be a class. Change Password, Register New User, Post Feedback– every process can be its own class. The advantage is that each process becomes independent of the rest of the system.
As I’ve traveled down this path, I find myself moving closer to some of the concepts in Domain Driven Design (DDD), Command Query Responsibility Segregation (CQRS), and especially Event Sourcing (ES). I like that ES designs for atomic events with specific handlers. I like that CQRS is quick to separate processing that changes the data from processing that queries for the data. The “bounded context” concept from DDD is, perhaps, the most important concept when developing a system of any size. In general, it’s because it represents isolation of business concepts. It’s all about isolation. In software development, isolation is paramount if a system is to be flexible, extensible, scalable, maintainable, etc. We see isolation again in the Single Responsibility Principle from the SOLID programming principles. That’s referring to object design, but notice how all of these isolate behavior. Some at a micro level (SRP), and others at a macro level (bounded context).
Used appropriately, these are all tools of isolation: methods, delegates, interfaces, classes, inheritance, namespaces, assemblies, services. Early on, I didn’t do a good job of isolating at all. Now, interfaces are small, specific, and numerous but organized. Delegates are welcome, not ostracized. I’ve changed my thinking a lot. Have I swung the pendulum too far? I suppose time will tell, but this is a far better position than where I was, and I’m excited for where I’ll go next.