- Home
- LLBLGen Pro
- Architecture
Antipattern? I think not
Joined: 13-Mar-2005
What do you guys think about Fowler's assessment here?
Joined: 17-Aug-2003
Don't worry. Fowler is also a guy who propagates ad-hoc db schema alternations. Well, I can assure you, that's a lot of fun with a large app
Some time ago I've stopped to think in 'right' and 'wrong' when it comes to software engineering. There's always something to say about a given way of doing things, i.o.w.: it's about the context in which you use a method ABC that makes the usage of that particular method a good choice or a not that good choice.
Joined: 26-Oct-2003
I actually agree with this. The more time I spend developing non-trivial business applications, the more I become convinced that many people (including myself) take the "entity" approach too far; or, at least, we misapply it.
The fundamental issue here, I think, is the OO concept of separation of concerns. This requires that each layer in the pipeline is responsible for one thing and one thing only. So, where is your business logic? In the business layer. What is the business layer? Well, some people think it's a set of objects that represent the various business "things" that are being worked with at a semantic level. Others prefer to extract the business logic into a discrete set of "controller" or "manager" objects that operate on DTOs. However, fundamental to OO is the idea that objects are behavior, not data.
The problem is that if we want to have rich objects, we can't send those objects everywhere willy-nilly because now we have the real-life problem of persistence and context. In the distributed world, we have no idea where these objects' behaviors may be executed. Thus was born Service Orientation which attempts to create services where callers may access the business logic of an application without having to worry about chatty business objects, or even perhaps having to deal with those objects being disconnected completely.
So, I think the common reasoning goes, why go through all the trouble of creating rich objects when we have to create a services layer anyway? Let's just put the logic into these classes and send it the data to operate on. I think this is where most of us stop. Why? Because the tools and the paradigms we have available to us currently don't make it easy to continue the path of reasoning.
What if we could have both? I agree with Fowler: "ideal" is rich business objects taking advantage of the benefits of object orientation (that is, behavior) that are then made available to callers using a thin service layer that exposes the semantic functions of the business in a procedural way. (I'm a fan of Rocky's approach ) It's the BL "surface" that then becomes the pivot point of the application. Above it we have presentation and state aggregation; below it we have business operations and persistence.
But, in order to have this we must have different objects for each layer. Business logic can't be exposed above the surface, lest we lose encapsulation and control over the execution of the logic, so ** we can't use the same set of objects throughout the application**.
We need presentation objects, state aggregation objects, instruction objects (or messages) , domain/business objects, and persistence objects, each with a set of behaviors specific to their purpose. It seems like overkill, but I believe that it only seems like overkill because we do not yet have the tools to create and get between each set of objects. But to implement such a system gives one higher levels of maintainability and reusability, as well as higher level of self-documentation and clearer code than would otherwise be possible with a single set of objects.
While this may not be a popular opinion, my personal belief is that the default objects generated by LLBLGen Pro are at their best below the BL surface. While there is some state aggregation and presentation logic built into them (witness field state rollback and databinding as examples), I believe these are incidental and only useful in the simplest of applications. Entities and collections in their normalized forms are only marginally useful in the presentation tier. Because the entities are implemented as graphs of objects, they lose their usefulness as DTOs to the business layer (what exactly does the absence or presence of an object in the graph mean?). Beyond the DAL and BL, I think the best use of LLBLGen Pro in this context is to exploit its excellent code generation framework and build upon the existing templates for entities and collections to create purpose-specific objects whose structure can be tailored to the need at hand. I actually think Answer did something similar to this for his web services implementation...
Now all of this being said the existing paradigm is still a fantastic way to create a powerful, scalable, and maintainable application. It's ages better than creating any of the objects by hand, and will absolutely save you weeks or months of manual coding time. My point is that we need to take the next steps in code generation and frameworks to really take advantage of object orientation, separation of concerns, and maintainability when it comes to how we manage the flow of data in our applications.
Jeff...
<Edit> D'oh. My diatribe posted after Frans did. ahem. If you don't hear from me within the next 24 hours or so, send help.
Joined: 10-Feb-2006
Nice assessment jeffreygg. This is an issue that my company has been struggling with for quite sometime now. We finally made a decision to go forward enforcing explicit boundaries between the layers of our architecture. So we have ui helper objects in the ui, message response/request entites and state aggregation entities in the service layer, dtos and business objects (behavior included) in the bll and llblgen pro entities in our object persistence layer.
We have found that this architecture takes a lot more work up front because defining service endpoints with the appropriate level of granularity is not an easy task and of course there is more code to be written to support mapping/validation/translation of entites as they cross boundaries. However the ROI comes when I get to provide an estimate of 2 to 3 weeks of development time to build a new application that would have previously taken 4 to 6 months to build b/c we don't have to "rewrite everything again". We are finally truly getting reuse at the service level. I am convinced that we are finally achieving reuse because we have explicit boundaries at each layer and have eliminated those dangerous dependencies that entities which are shared across 2 or more layers always introduce.
As for tools for supporting end-to-end development of an architecture like this, they are simply not there yet as you mentioned, however we are finding promise in the work that is being done by the MS Patterns & Practices group around Service Factories. They also have some pretty nice process guidance, a reference architecture and a fair amount supporting documentation that provides some answers to the questions like "Why so much abstraction?" that are usually asked about this approach.
http://www.gotdotnet.com/codegallery/codegallery.aspx?id=6fde9247-53a8-4879-853d-500cd2d97a83
Joined: 22-Feb-2005
Could you elaborate on how you handled the mapping layer between LLBL Entities and your custom Domain/BL/DTO Objects?
A lot of people talk about how they use this sort of design, where the LLBL Entities do not pass through the business layer. We did a project where we used this design, and the mapping layer was quite tricky (and time consuming) to write.
How do you handle things like:
- Mapping Entity => DTO and back (reflection? code generation?)
- Comparing collections for insert/update/delete (do you have a custom UOW object?)
- Databinding for DTO Collections?
- Saving (do you re-fetch the LLBL object and compare, then save)
If anyone else has used this sort of design, I'd love to hear their thoughts as well.
Thanks,
Phil
Joined: 10-Feb-2006
We actually treat our BLL boundary much like we treat our service boundary. In our services layer we take in request objects and return response objects modeled after the approach outlined in the Software Factories reference architecture (reference link in my previous post). In the service layer we use the tools in the guidance package of Service Factory to generate translators that map request/response objects to/from BLL DTOs. The tools are pretty rudimentary so they get us about half way there, we have to manually code the rest of the mappings.
In the BLL layer we use the Assembler pattern to assemble outbound DTOs and we receive inbound DTOs into static methods in our manager classes, this unfortunately requires manual coding. The inbound DTOs are generally just entities that encapsulate the properties required to complete a given UOW in the BLL. So in essence, we have messages that go in and out of our service layer and messages that go in and out of our BLL.
Inside the manager classes in our BLL, we create domain objects in some cases from the properties in the inbound DTOs that encapsulate LLBLGen entites to do the work, but we only use domain objects in cases where we have identified that the work being done is "significant and reusable", significant meaning there is some actual set of business rules (beyond simple validation) that need to be implemented. We provide very flexible and customized business rules for our customers that match their workflow processes (can be different for each customer), so we are actually abstracting our business rules (rules like if A and B and Not C then do Y) into a pattern that makes it possible for us to plug in different rule chains for each site by simply replacing the BLLRules assembly.
In other simple cases like GetLookupsByCategory, we just use LLBL entities directly in the methods exposed by the manager to retrieve data, usually as dynamic typed lists that are then assembled into DTOs by the assembler. We do a lot of flattening here, we have a data model that is pretty close to 3rd normal form with lots of lookups and fk relationships so we construct our DTOs so that those relationships are flattened out before sending them back to the service layer. Then in the service layer we map the DTOs to response objects to support abstraction in the service layer to prevent consumers from being tightly coupled to our BLL. This also makes it easier to achieve the appropriate level of granularity in the service layer and makes it possible to support service aggregation so that we don't have cross BLL dependencies between our core entities and our application specific BLLs.
It should be noted that I would never recommend this approach for a single application architecture. We have a true "enterprise" environment in which we may go into a site selling 3 or 4 of our applications all of which share a common set of core entities but also have a need to communicate with each other. We expose the core entities as isolated services and we also expose APIs for each specific application as isolated services, our applications talk to each other via these APIs and likewise access the core entities through their defined service endpoints. The "abstraction ad nauseum" here actually does provide us with a lot of benefits in that environment, but I would definitely argue that it is not at all warrented in a single application architecture.