How Many Classes Per Business Object?
In ColdFusion you're almost required to have at least two class files per business object. Because of the lack of support for class methods, you really need some kind of Singleton to mock that, and to me, one of the key uses for a Service layer is to provide for class methods on the business objects (more about that in my next posting where I'm going to argue the benefits of one service class per business object). I also like to abstract out my db code and have seen that as a standard practice in most of the web development languages I've played with. It both makes it easier to switch db implementations (which may or may not be a big deal for many developers, but for me is actually quite useful) and makes it easy to find all of your SQL code. Because of this, I find that three classes per business object works really well for the projects I build. For example, if I have a User and Article business object, I'll have User, UserService, UserDAO, Article, ArticleService and ArticleDAO business objects.
The only real objection I can think of to this is that you end up with quite a lot of files. For instance, in my system where I only have to create code if a class has a method that can't be described declaratively (again, more about that in a future posting), about 80% of my class files don't actually have any code. However, I solve that simply by not creating the class files I don't need. It's a simple technique, but I think I'll save that for another posting so I can explain it thoroughly.
What's your approach? Typically how many classes do you have per business object?



User, UserDAO, UserService
Group, GroupDAO, GroupService
SecurityService (has UserService and GroupService passed into it via ColdSpring for login, authentication operations).
however, this is a service oriented approach: in a pure oo approach you have to encapsulate properties and methods in one single class, so in this case you'd have one class for the order example.
regards
salvatore fusto
ServiceFactory? Business Layer, DAO, Gateway, Bean? For God's sake.
When did coding in ColdFusion become so freaking complicated? I have a book that I'm working through "The Object-Oriented Thought Process, and while it's main focus is Java, it proposes exactly ONE class (CFC) per "verb". I will never understand why you guys try to make things so complicated and convoluted. One of my coworkers uses 3 CFCs for any thing he tries to do in CF and it just seems so over the top. It takes me like 15 or 20 minutes just to unravel what's calling what, and how things are instantiated that it makes me want to rip my eyes out.
If you're doing something with a verb, or object, why not just put everything in just one CFC? It makes things so much simpler for ANYONE to follow.
@Andy I get you're frustration, but be wary of using one object for everything. You risk the "god object" phenomenon and are likely coupling code that will cause problems if you need to change something down the road. If your co-workers uses UML, have him draw out the object composition for the classes he creates. It will be much easier to understand the path of delegated method calls. As an added bonus, you'll get a visually documented API.
One thing I don't understand that is a common thing that seems to come up, so I'm honestly asking here, why are some folks against additional objects simply for the reason that it's more objects? I'd rather have 10 objects each of which is well encapsulated as opposed to a single object that does everything under the sun. To me the object that does everything is far more difficult to manage than a higher number of discrete objects, both during development and certainly during maintenance.
http://www.iknowkungfoo.com/blog/index.cfm/2007/8/...
I'll be posting about Service and Factory objects soon.
@Andy: I understand your frustrations, but once you get into using OO concepts on a regular basis, you should start to see why using multiple objects with small amounts of code to handle certain processes can be a good thing. Of course, you can go overboard with anything, KISS applies to OOP as well.
It's a genuine question as I'm not saying that I'm not missing something - just that I'm stll looking for a good concrete example of where the 1:1 service to business object and a little bit of auto-generated ORM doesn't solve the problem. To date, whenever I've seen an example, what I've really seen (to me) are additional candidate domain objects.
Examples appreciated!
@Phil, Definitely no "one true way" (well, except MY way, of course :->). In your specific case I just have a User.authenticate() which may just ask the user what groups is has using ORM, so I dont need to write the code. Agree you could add a SecurityService, but I'm just going for the simplest thing that can work!
@Salvatore, But through ORM your Order has Items, so while I have an API by default for accessing items, I'll just go to the Order service to get the order loaded - including its associated items.
@Andy, Until that CFC is 12,000 lines long and then it is a pain. Also, because there are no such thing as class methods in CF (unlike Ruby, Smalltalk and even Java) you're probably going to NEED to have both a business object and some place to stick the code that relates to collections of such objects (which I break up between the Service class and a DAO for database access). It's frustrating to learn, but works really well when you get into the flow.
@Paul, I'm still trying to get the methods that a BillingService would have that I couldn't just call drectly on the Order, Invoice, PaymentTerms given defined relationships between them. Not saying they don't exist - just need the right example to get me over the hump!
@Matt, I think number of objects is a goldilocks problem. Too many files is too much jumping in and out of different items. Too few leaves you with too much scrolling and more file update collisions in a collaborative environment. The answer is to have "not too many, not too few" files, but because it doesn't really matter if you have a few more or a few less, I think the goldilocks point varies between developers, driving some of the disagreement we see on lists.
Personally, I try to put each unique object into one CFC. So if there was a User object, everything applying to the user would go into the Users CFC. What's the point in having a Gateway that calls a DAO, which in turn hands info to a Bean, which just returns the exact same info to the DAO, which then passes this info back to the Gateway, and finally back to whatever called it. Yeah, that efficient. Why not just combine all of these steps/CFCs into one CFC?
It just seems that people are trying to make things so much more convoluted than it ever needs to be. I don't want to create a headache for myself just so I can say I'm using OOP.
Ever considered maybe they're doing this stuff because it's solving problems they have run into that either aren't relevant to your use case or that you just haven't come across? What are OOP bragging rights? I write OOP code because it means I can maintain more complex applications, more quickly and easily. I run a company. The "completixities" I use make me more profit i less time than the alternatives - plain and simple.
Heck, forget OO. Now I'm into DSLs and SPLs and all other kinds of TLAs, and you know what - they're making me more money in less time too. But if you don't need OO, don't use it. Unless it seems to be solving a problem in your world (or you just want to improve your programming skills for career or personal development purposes), don't mess with it. Use CFC's as you will or even stick with custom tags and includes - there is nothing wrong with putting code at the top of your script pages - until it because a maintainance problem. And there are lots of ways of solving that. OO isn't the one true way, so do whatever works for you.
Sorry to hear about Andy's problems. I find occasionally blog CFC does that. Please ask him to save contents of comments field before submitting just in case No conspiracy! I'd love to hear what he has to say.
Sound ridiculous? Of course, but no less ridiculous than the opposite. I am seriously tired of the anti-OO crowd hijacking decent conversations. Don't want to use OO? Don't (just please don't ask me to maintain your uber-user.cfc)...but what compels you to join in a conversation that doesn't apparently interest or concern you? What so different than those comments and your typical blog troll? I am sorry as I am not meaning to insult anyone, its just this conversation is old and silly and pointless and actually has nothing to do with the question Peter asked IMO.
Anyway, back on topic... Peter, what is wrong with Paul's example? Seems like a perfectly valid example to me.
Amen!
Good question! Firstly, Pauls example is perfectly valid, and I quite like it as an example. There is nothing I could or would want to say against that approach. Here is my situation. I want to build applications as quickly as possible, generating as much of them as possible while still consistent with them being well architected and maintainable. I am currently doing quite well by generating one service class per business object (actually I only create the physical service classes for business objects that have custom service class code which is usually maybe a third of them - the rest I just instantiate the base class which keeps my directories nice and tidy).
What I like about the 1:1 approach is that I have a DSL for describing business objects. You describe the table name and the title, what editing methods are allowed, you describe all of the properties (including calculated and associated properties) and their rich data types so I can gen all the admin code. You describe their validations, transformations and their relationships to other objects and you declaritively describe any class methods that don't need to be hand coded (anything that could be done as a parameterized call - stuff like deleteExpiredCarts() or getAdminUsers() or the like).
You put that XML info a config directly, boot up the app and it automatically gives you a working application with rich admin features, versioning, rollback, all kinds of other goodness. To date I have not run into any problems with this approach, but I am concerned as there are two reasons why you don't run into a problem. One is because there isn't one, and the other is because it's just around the bend you're taking at 90mph and you just don't know you are heading for a train wreck!!!
What I'm trying to identify is whether my simpler approach is heading for a train wreck. What I'm looking for is an example of something that just plain wouldn't work with my current approach or that makes it look really unwieldy to work with. If I see such an example, I can either decide it's outside of my use case, change the way I do everything, or provide a new extension point to deal with such cases. So what I'm really trying to find is a case where my current approach will really suck as opposed to examples where other approaches would also work (which is more what Pauls example feels like to me).
None of that is to say my approach is better than what Paul suggested. I'm just looking for cases where what I'm doing is clearly worse!
Make sense?
In a complex system you're going to have lots of "thingies" no matter what you do, and whether those "thingies" are CFCs, functions within a CFC or just branches within a function matters not as much as whether you let them clutter your API. I suspect people react to Peter's use of the word "Service" by assuming that he means that he's created lots of public API thingies. But nothing Peter says precludes the idea of slapping a simpler, more abstracted facade on top of this whole business object layer.
My own approach is much like Peter's. Conceptually it's three classes per business object. However (similarly to what Peter alludes to in the last para) I can usuallly use a generic DAO, and in limited circumstances a generic business object, so a lot of the time I can boil it down to one or two classes per business object.
Jaime
For me the cases for a multi-object service layer are where you have:
a) a non-OO client
b) an OO client that can't consume your objects natively, or
c) a remote client where you want to minimize trips to the server
If writing public web services, I'd assume I have to cater for at least a and b, and probably c is a concern as well.
However, if you can bring the domain model into the client process and there's no reasonable prospect that you might have to go multi-tier, I'm with you, just use the fine-grained API inherent in the domain objects.
Jaime
@All, I'll echo Phill's original statement that it really depends on what fits your style. I don't particularly like to follow a trail of method invocations from one service to another, or manage decisions about whether to use setter injection or mixin injection to resolve circular dependencies, so I try to keep the management of my related BOs (likely composed objects) in a service with a name that describes business case.
To be honest, Peter, I hand code a lot still and use Brian's code generator to get over the really tedious grunt work. With code generation and DSLs, it would be more work to add the grammer to merge Service classes. I think you're looking for a potential problem where there isn't one. As long as inter-Service dependencies are manageable a 1:1 Service to BO ratio is golden. The only other scenario I could suggest that might get you into trouble would be database transactions. I've never had the problem yet, so I'm not even sure that's an appropriate example....
@Paul, Transactions can be handled by the ORM, so no problem there.
@All, Many thanks for the input. I'm not seeing any roadblocks yet, so I think for now I'll stay with my default position of 1:1 as it works well for my use case, but I definitely buy a different ratio as an alternative - possibly slightly nicer - approach for hand coded apps.
If I do hit any speed bumps I'll be sure to blog about them!
As I explained in that chat session, I tend to have a bean per noun and those will be as smart as necessary to model the noun but my data gateways will handle groups of related beans. E.g., in the e-commerce example, invoices, orders and items etc are all closely related so I'd probably have an InvoiceGateway that handled persistence for all three. I don't use separate DAOs, mostly because I use an ORM to handle that sort of SQL for me. Then my services are aligned with workflows - and may not directly match even a specific gateway, e.g., an OrderService for managing the ordering workflow that deals with user, invoice, cart, order, item objects etc. Then my controllers align with the UI concepts, e.g., CartController, ProductCatalogController.
However, the rules of thumb I use to arrive at that organization are hard to articulate so it's very difficult to provide guidelines that would help others reach the same place. Really only experience can determine how you make some of these choices.
I sympathize with Andy and Eric because the "5:1 Syndrome" (bean + dao + gateway + service + controller for everything) makes OO look much, much harder than it needs to be. Not that OO is easy - modeling your domain well is hard and something that takes lots of experience, just as good programming in general takes lots of experience. CF is both simple *and* powerful and it allows for a very wide range of programming styles (much as C++ was described by its creator, Bjarne Stroustrup, as a multi-paradigm language). If you're familiar with OO and tend to think in objects, an OO design is going to be easier and more natural than a procedural design. If you don't know OO well enough, a procedural design is going to seem easier.
Great feedback - thanks. Brings up some questions:
1. Where do you put your ORM calls? Always in the bean? Sometimes in the service? Ever in the gateway?
2. If you had a case where you needed some hard written CRUD methods (say to interface with stored procedure CRUD in an legacy app but where you only needed to do this for one or two business objects in a larger app), what would be some of your first thoughts on where to put such CRUD?
3. Sounds like potentially not only could you have less than one service per business object, but sometimes more than one service could deal with the same business object? For instance, you could interact with users via an AuthenticationService, an OrderService and a TaskService. When handing off code to others do you ever run into issues where they are not sure where to look for all the code related to users because service layer code related to that could be spread between n-service classes and do you ever run into issues with reuse mapping the necessary dependencies between controllers, service layers, beans and gateways ("oh darn, my checkout controller needs my order service which depends on my user, order, shipper and tracker business objects and to make them work I need my order, admin and shipping gateways")?! On the one hand it seems like your approach is more elegant than a 1:1 as it clearly separates concerns by task/related methods which is typically how stories are developed, but it seems like getting up to speed with any given such implementation could take a little longer and it might make reuse, and training of new devs a little more complex as you have more layers of distinct semantic meaning that need to be mapped.
Any thoughts?
2. For me, CRUD goes in the relevant data gateway object - as I said, no DAOs in my applications these days.
3. Yes, multiple (workflow) services could indeed operate on the same business object. The separation by task means that maintenance focuses on behavior rather than "nouns". In reality, you don't need to find "all code related to users" because you have a rich user object containing all its behavior and then you have workflows that are relevant to the business domain and you're normally focusing on code to support a workflow or business process.
The trouble with the 1:1 (or 5:1) models is that you have very little coherence - the high-level "controller" (often an XML configuration in the CF framework world) ends up talking to several controller CFCs which each in turn talk to several service CFCs and that's much harder to work with and maintain because you can't look at a *process*. The 5:1 model also tends to create anemaic domain models because it pushes the focus onto the controller and service, both of which end up being fat and somewhat procedural and those just talk to dumb beans that do little more than contain data.
Of course there's also the reality that many of our applications are little more than basic data management so there aren't many workflows and the beans often *are* dumb by definition. I think that's why the 5:1 model has taken hold so firmly - it "fits" a simple data management app but it sure seems unncessarily complex. If all you're really doing is basic data management then the layering is counter-productive: you might as well have a simple controller per UI area and talk directly to simple data gateways for related groups of beans (and then the beans themselves). In other words, in the absence of workflows, the data layer pretty much *is* your service layer.
Last summer, I built an GWT application, which is a Java application that gets compiled to client-side Javascript. The program worked like this: a user did something to the UI, the application dispatched an RPC call, an asynchronous handler processed the reply, then any UI elements that changed on the server had to be updated on the client.
There were many problems when "verbs" were methods of "noun" classes (GUI Widgets). For instance, each "verb" requires at least two methods: one to do the RPC call and one to process the response. Second, and more seriously, users could sometimes trigger the same action by doing different things to different GUI components. Finally, a "verb" could cause several UI components to be updated, not just the "noun" that the "verb" supposedly belongs to.
The app was greatly simplified by creating a package full of "Command" objects that handled the various things users could do. The UI code would determine the RPC arguments and do something like
(new WhateverCommand(arg1,arg2,arg3)).execute()
and it was done.
Think of a web framework which has URLs like
/controller/{noun_id}/{verb}
The controller sees the {noun_id}, instantiates an instance of the {noun}, then calls the {verb} method. This is pretty simple, but it's got it's own conceptual problems. For instance, a verb can exist without any arguments...
/controller/logout/
There are basic conceptual problems when it comes to representing relationships in OO systems -- for instance, do you write
user.addCapability(), or capability.addUser(), do you write both? Is it
/controller/{user_id}/addCapability/{capability_id} or the other way around?
A conceptually clear approach is to write
/controller/{verb}/{argument1}/{argument2}/...
i.e.
/controller/logout
/controller/get/{object_id}
/controller/associate/{object1}/{object2}
"Verb" objects can work together with "Noun" objects to exploit polymorphism in simple, powerful and flexible way. For instance, the "get" verb could (together with helper classes) worry about cookies, headers, HTML and other webby stuff, then instantiate the object and call a much simpler "get" method on the object. You could also divide the "get" function into N phases with N different methods, providing an efficient way that object subclasses can override just one phase of the "get" function.
Stop worrying and learn to love objects!
Logout is really /controller/user/logout - it's a user-based operation.
With your user/capability example, it depends on the primary relationship between the objects but user.addCapability(theCapability) sounds more likely so /controller/user/addCapability/{capability_id} - you'd normally be operating on the current user.
Or /controller/admin/addCapability/{capability_id}/toUser/{user_id} if you're performing an administrative operation - or /controller/admin/modifyUser/{user_id}/addCapability/{capability_id} depending on your preference (the underlying operation doesn't change).
The Gateway and DAO stuff was started back in 2003 as a fix and was not correct OOP. Cfc's have evolved since then, we should stop creating DAOs and place the CRUD in the Gateway object.
Service cfc's are great in the model layer as long as they ONLY contain business logic. A service cfc interfaces with other objects where the business logic has to draw on an outside source of data, such as a database for local data or webservices for remote data.
Data should be strictly under control of the model layer through its object services in this manner, webservices and etc also. Under the model directory then might be service cfcs, then a bean directory and a gateway directory. The bean directory holds object bean getters and setters, and the gateway directory has one gateway cfc for each table in the database. If you create DAOs your splitting your database table objects in two, wasting time and creating an overly complexed bloated file environment of redundant code.
I'm sure that this may or may not pertain to all situations but I have found peace since dropping my DAO habit and you will too.