Javier poses the question: “how many [CLR Objects] does it take to represent a Real World Object?” I have posted in about this in the past, but it’s an excellent question that throws a lot of folks. I also think I’ve refined my opinion on the question and it’s good to revisit these things every so often. The question made me think back to an old school commercial for Tootsie Pops that aired during Saturday morning cartoons:
In the commercial a boy approaches an owl, Mr. Owl to you, with a question: “how many licks does it take to get to the center of a Tootsie Pop?” Ever the empiricist, Mr. Owl engages in a short experiment. Loosing his discipline after three licks the owl bites into the candy’s chewy, pseudo-chocolate inner core. The owl licked thrice before chewing. He asserts, therefore, it takes but three bites to get to the center. The boy seems dissatisfied with the conclusion of Mr. Owl’s investigation worriedly observing that ”the world may never know.”
The number of objects you need to represent a real world entity can vary greatly depending on the needs and architecture (which should be driven by the needs) of your application. I’m still a believer in one entity probably isn’t enough. Let’s introduce an example. Say we have to represent Customer somewhere in the application.
My first question is what makes up the Customer? Likely they’ll have one or two address objects? Let’s say they have two, a shipping address and an office address. So far we’re up to “thu-ree” - as Mr. Owl says - objects. Customer in this sense is a composite or, in DDD terms, an aggregate root. So right we’ve got (potentially) an effective infinite number of “objects”. Let’s make “object” mean “type” or “class” going forward. If we recast the question as “how many types does it take to represent a real world object” we can come up with a finite number of associations be they many-to-one, many-to-many (association class), or one-to-many (collections). Still I like the idea of “Customer as boundary” that comes with the DDD notion of aggregate root.
So how do we get that Customer to our application layer? Generally speaking I look for an organizing principle (information architecture) centered on the main entities of the application. I like to group screens and actions in the UI around the entity. So I’d like to come up with a screen that lets you act on a list of Customers or open an individual customers, browse its data and take action on that instance of customer. Even though there’s a certain nakedness to these business objects I tend to favor creating DTOs or messages that represent the required slice of data. For example, I’d consider a list-like DTO of Customers that’s contains data that helps you find an individual Customer represented by a more jagged, higher resolution detail DTO. I also don’t invest too much time in factoring my DTOs for re-use, so I’m apt to create a DTO specific to the purpose at hand.
Sidebar: I use a base class and external DSL for data transfer objects and have them support data-binding. Read more about it. I know CSLA, for example, goes with a kind of single entity approach. Rocky calls it “mobile objects.” That is a customer is a customer is a customer. I don’t like this because I feel that it creates a lot of transformation code everywhere and I like to centralize this in a Service Layer. I also avoid loading my entity classes with responsibilities orthogonal to business logic such as data-binding support. Instead I favor a Service Layer peopled with Application Services. An Application Service is really a facade to the Model layer that brings infrastructure (sending an email) in to the mix with Domain artifacts: Entities, Repositories, Factories, and Services.
Now we’ve got all these messages that support a user interacting with the model of a “real world object” and we’ve got a number of MVP triads that ultimately construct these messages that work on the entity. I guess my answer is as many as it takes and it can take quite a few. The benefit of splitting a real world object into a number of tailor-made (for the layer) objects is that it preserves a layered architecture which benefits through partitioning and isolation. That’s a Byzantine way of saying layers make a code base easier to evolve/maintain.
If I had to break it down to for my current default architecture for smart clients that work over a firewall:
-
Customer (Aggregate Root Entity)
-
Address (Value Object)
-
CustomerRepository (Finds/Saves/Deletes Customers)
-
PreferredCustomerService (Gets a list of Orders for a Customer and determines if they’re standard, gold, or silver)
-
CustomerService (CRUDdy Application Service for Customers)
-
CustomerDetail MVP Triad
-
CustomerSearch MVP Triad
All of these items are required to represent the real-world customer given my architecture. Even if you go directly to model you still need to display stuff. Even if you’re not using MVP and are using Autonomous View, it’s still another object required to “represent” the real-world concept of customer. My preference is simply to let the concept of “Business Entity” flow through a layered architecture when it’s natural to the end user. In general I think it is a natural concept for a lot of entities. Adjusters talk about working with claims; underwriters write, renew, and terminate policies; project managers manage, well, projects…
Having this baseline set of patterns helps you avoid the trap Mr. Owl fell in to. Namely, patterns control how many licks, um, objects it takes to get to the center of a user story and at the heart business value. You don’t need to go nuts with an over-arching, prescriptive framework. Pick a few design patterns to start with and a framework will emerge over time as you suit immediate needs, ease frequent pain points, and address redundancy. I, personally, look for how entities are treated, look to them (initially) for an organizing principle of user experience, and find design patterns that mesh my findings with a good measure of maintainability and testability.
6 Comments
I think the question could be taken many ways:
How many classes does it take?
* ignoring proper design principles… one!
* one might argue also….. no more than is absolutely needed. Resist the urge to put in tons of flexibility where it is not warranted
* and, like you say, to accurately represent a real-world object you’d need a plethora of classes that model even the tiniest behavior.
Interesting note on the sidebar… I like your take on just giving certain screens the “slice” of data that they need and nothing else. That frees you to model your domain independently of the specific UIs as long as you keep up the translation. I’m curious how you manage to have many different “flavors” of DTO, depending on the consumer… perhaps another blog post?
I like your answers. They’re far more direct! :)
I’m coming to think of DTOs as pretty much messages. I’m just reading Gregor Hohpe and Bobby Wolfe magnum opus on Enterprise Integration Patterns and digging back through Udi’s blog. Recently I’ve been mostly in application stacks so I try to keep it simple. Still, I’m a big fan of a “service membrane.” I find by making it part of the default architecture, seperating out contracts (interfaces and DTOs) into a module that clients (UI, integration services) take dependency on _greatly_ contributes to partitioning and maintainability.
Even w/ the slice approach it’s not too thin a slice. Sometimes you find occasion to use make two DTOs fill in for one purpose. I think (keyword) that I’ll probably come around to the idea of each operation having a _distinct message_ (request, response, both, none) and DTOs are more the elements used to compose these message. So like in XSD parlance DTO as complex type. Message as root element.
Developers moving from Structured Procedural code to Object-Oriented code often notice the resulting “class explosion.” Generally speaking, it’s a sign you are learning better habits. Class Explosion can also be a smell, but when you are first switching, you should see a huge bump in the number of classes you create. You can learn the smell later.
Anyone care to weigh in on Factories from DDD? Where does your Customer start its life (ie..before it was persisted the first time)?
True re: class explosion. Role interfaces, to me, are the next step in OO evolution.
I generally don’t use standalone factory classes. Instead, I like a mix-up of the old skool GoF builder pattern but external. I’ve called them in the past a build-to-order specification, but I think it’s really a GoF builder. I’ll feed a builder into a Factory Method living on a repository. I try to keep aggregate roots in control of creating it’s entities in a Demeter-safe (which I wholeheartedly believe in as a core design tenant (excepting method chains)) fashion.
You can have one or more Builders that deal with the invariants and at that point you can persist the class. I like Jimmy Nilsson’s idea of a least-restrictive sense of persistance. That is, I think it’s cool to go ahead and persist entities that are broken. I’ll just persist the reasons they are broken.
Yes, no, maybe, ha ha?
Great, post dude! I particularly like the reference to the Toosie pop. I was thinking of naming my blog post, “how many licks does it take to get to the center of an app?” … however, like always you did a better job!
…
It reminds me of the conversation you and and I had on the way to the MS campus back in March. One point that really sticks out for me is this:
“All of these items are required to represent the real-world customer given my architecture.”
To me this is huge! It’s context, context, context! Sometimes I think we get too involved in the “right & wrong” arguments of development and stop focusing on the core of application (what does it need to do and for who?).
We all just need to remember that OO at its core is the passing of messages to objects (yes, objects not classes) and that’s it. The fact that you have encapsulation, inheritance, blah, blah…that’s just icing on the cake.
@Javier - I don’t know about like always. I really like the stuff you’re doing w/ IronRuby. I agree developers, as a species, get enamored with the single right way. I try to keep it simple but I do find that having a few, well-placed constraints helps to move things along.
Post a Comment