Object orientation is a fascinating topic. It is one of the core concepts for a major part of developers today, yet the understanding and the inclusion of it in our day to day work varies greatly. Even though many believe they do the same. 'Cause everyone is working object oriented with an object oriented language, right?
We had a day back in December at Objectware where we revisited object orientation, and its implications for our work. The day was structured with some presentations first, a good deal of group work, a reflection on the work, followed by a general discussion in the end. We had a continuous focus on discussions and experience sharing throughout. I started of with a session on general everyone-should-know information, more of a recap or introduction if you will, and then brought the focus to the use of object orientation in structuring our business logic, centralized or delegated design, and so on. A few colleagues of me followed on with topics concerning cohesion and coupling, information hiding and composition vs inheritance.
I thought I'd share some of the content of my presentation here. It's certainly not a new topic, it has been blogged and written about countless times, but still I meet a lot of variation on the knowledge in this area.
First of all - how is this valuable to you? Knowing how to structure your business logic both in various forms of applications, but also varying internally in an application, is paramount in ending up with an application that doesn't fight you continuously. In designing every application we want to:
- Create an architecture that meets all technical and operational demands
- AND that solves all quality attributes like performance, security and maintainability well
The architect should set standards concerning the business logic layer, but I've seen more than one architecture transform into something very different because of varying knowledge on this. It's always important to know the main differences so you can take informed decisions. This is one of those areas that most software developers should know - especially those that work on applications containing more than a trivial amount of business logic.
Object orientation is about core principles like abstraction, composition, inheritance, encapsulation, polymorphism and decoupling. Several things can be said about this style of programming, like
”Division of responsibilities for an application or system into individual reusable and self-sufficient objects, each containing the data and the behavior relevant to the object.”
”An objectoriented design views a system as a series of cooperating objects, instead of a set of routines or procedural instructions.”
In general it can be said that Object Orientation should be considered when:
”You want to model your application based on real world objects and actions, or you already have suitable objects and classes that match the design and operational requirements. The object-oriented style is also suitable if you must encapsulate logic and data together in reusable components or you have complex business logic that requires abstraction and dynamic behavior.”
(All quotes from Microsoft Application Architecture Guide, 2nd Edition)
And you continuously work to
- find the right abstraction of the real world for your problem
- find fitting objects
- find the right place to put code
- rework the code at all times to make sure it is correct based on your current understanding
- favor low coupling
- limit duplication
- make each object work on one thing only - ensuring high cohesion
- work to have a test friendly design
- and generally try to keep your code as to-the-point as possible, all the time ensuring that adding further behavior and extending the current functionality is as painless as possible (without over engineering of course :) )
- and certainly much more than this..
How you structure your business layer certainly has a say in how well and how easily you can achieve these things.
You traditionally have four main ways of structuring this logic:
Procedurally oriented with Transaction script or Table module.
Object oriented with Active record or Domain model.
Let's take a closer look at each..
”Organizes business logic by procedures where each procedure handles a single request from the presentation.”
Martin Fowler, Patterns of Enterprise Application Architecture
Transaction script follows the current structure:
Note that this is an extremely simplified example. Using transaction script you certainly utilize all you know about class design and move logic into where it fits best, the main point I'm trying to make is that you have a central place to control the flow. The CreateOrder method controls what logic will happen from start to end.
If you use this all the way, the business layer will consist of a number of procedures that each implements one action from the user interface. Good design in this context is about minimizing duplication at the same time as meeting all demands. This has nothing to do with a database transaction, but refers to one monolithic logical operation. It is not uncommon to create one business component per database table.
The positive sides with it is that it's easy to understand and maps well to the flow of a user story/use case. But it breaks down on complicated logic, can be hard to test as it does many things, and can lead to duplicated code.
Being popular in general, it the .NET world it was quickly replaced in popularity by....
”A single instance that handles the business logic for all rows in a database table or view.”
Martin Fowler, Patterns of Enterprise Application Architecture
The Table module is built around a table of data. Its perspective is similar to that of Table script, except the focus is on a group of data. Operations are often designed as methods on an object that represents a table of rows. Because of that you always use a key or index to indicate the row in use. It is procedural, but has more of an object oriented focus than Transaction script.
Note that you don't need to encapsulate the DataSet in a custom class, it is common to just work directly with the DataSet as well.
Table Module quickly became a success in .NET because of the great tool support existing. Putting together a simple read-only application that shows data in grid form literally takes no time because of the focus Microsoft has had on the tooling aspect. Everything from GUI to database works smoothly for this. Generally you can say that RAD applications work great with this approach. And it's a fair compromise if you have little logic and don't need much abstraction over you data model - because there will be a very tight coupling between your data model and every part of the application.
A history lesson
The Table module pattern has many downsides though - even in .NET. If you have complex business logic, it won't cope with that very well. It's very data driven, and doesn't focus on the business side of things. You will end up with an application that is very tightly coupled to the database. It is poor at indicating relationships between objects, and polymorphism isn't exactly available. As long as you keep to the code generation and wizard support available, you'll be all right, but it can get complicated if you can't rely on that.
Microsoft had for many years an almost exclusive focus on this pattern - by their way of action basically saying that you could solve any problem with a dataset. Object bigots found various ways around it, often by creating mappers that converted datasets to objects, by cleverly encapsulating datasets or by creating custom data mappers using the data reader directly. The focus since Visual Basics heyday was on RAD applications, supporting novice developers, and not all that much more (Patterns & Practices shipped Enterprise Library 1.0 in 2005 though). The Microsoft community didn't seem to mature much more either, as there was an almost sole focus on software and tools that Microsoft shipped. From what I have come to understand this made the whole community much less used to object orientation than for instance the Java community became. And that is likely the main reason why the general Microsoft community is still so procedurally oriented still.
Luckily things have changed, both in terms of third party tools and to some extent Microsoft's focus. Tools like NHibernate and the Castle Project led the way with support for good object relational mapping and dependency injection tools, as well as MonoRail as an early MVC framework. Microsoft has supported enterprise applications through the work of Patterns and Practices on Enterprise Library. However, simplicity and enterprise library has never been used in the same sentence. In recent times though, Microsoft begun focusing on object relational mapping through LINQ To SQL and Entity Framework, an own MVC framework, ASP .NET MVC and their own IoC container, Unity, and the continuing support for composite UI applications with PRISM for WPF and Silverlight (Following the Composite UI Application Block for Winforms).
So the tide has to some extent turned in the Microsoft community, certainly the support for the two remaining patterns are continuously improving.
But less take a closer look at the object oriented patterns:
”An object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data.”
Martin Fowler, Patterns of Enterprise Application Architecture
The example above uses Castle Active Record. As you can see, the object encapsulates one database row in principle, and the object contains both data and domain logic. It includes logic to directly manipulate the database, like Save or Delete, and also includes static methods that work on the entire collection, like Get, or GetOrdersCount.
The flow of logic is different in Active Record than in the two previous patterns in that the object contains the logic internally, making use of encapsulation and achieving higher cohesion. Instead of having the service dictate how the logic should flow, you ask the object to perform some domain logic, and although not shown here, it typically delegates responsibility to other objects.
Active Record is a useful framework for two reasons: simplicity and tooling support. It's quite easy to understand, and as long as you use frameworks like Castle Active Record or LINQ to SQL, it's also fairly easy to use. For simple object models it works great, and as long as it's OK to have the object model closely mimic the data model, Active Record is a good choice.
There's a few things you should consider before choosing Active Record though. Because of it's close connection to the data model, you have very limited support for designing your object model separate of the database. If you have a need for that, you should skip ahead to the next pattern. It's also a problem that it mixes responsibilities. The objects holds domain data and methods, but in addition you have attributes for mapping to the database, CRUD operations and static methods that work on the entire collection. And it sure isn't Persistence Ignorant (PI).
This is a popular pattern as well, and even though it seemed Microsoft tried to kill (http://bit.ly/8VkYnW) LINQ to SQL to avoid supporting it in addition to Entity Framework (more on this tool later), it's popularity seems to have saved it for now.
If Active Record doesn't quite do it for you, and your complexity is high enough, you should take a closer look at the next pattern:
”An object model of the domain that incorporates both behavior and data”
Martin Fowler, Patterns of Enterprise Application Architecture
The domain model pattern is the one that best supports object orientation. The point is to separate the domain logic into classes that are only concerned with modeling the domain and the corresponding rules well. The model classes should not know how to persist themselves (as in Active Record) and shouldn't be coupled to infrastructure logic. The classes should be POCO (Plan Old CLR Object), enabling a higher abstraction from the data model. This also means that the business logic of the domain model is simple to test, and you can easily get high coverage on this most important area without worrying about infrastructure and the like.
Domain model is the most complex to use, mostly because of the cost relating to mapping to the database (because of the impedance mismatch), and complexity regarding new ways of having to think about disconnected objects, lazy loading, less direct SQL to tweak and converning the next major point of this blogpost, not being familiar with delegated control. This complexity is biggest on first usage, and drops in subsequent projects.
Domain model shouldn't be used in all scenarios. Use it when you have a complex domain with complex business rules and when you want to be able to model the domain model free from database limitations. It is more complex to master than the other patterns, but in the right scenarios has great strengths.
To handle the persistence of your domain model you use an Object Relational Mapper (OR/M). This includes an API for CRUD operations, mapping between the database and the data and domain model and a query model and associated language. The modeling is usually done either via external XML mapping files or some kind of fluent interface, where you state how the model maps to the database. A lot can be said about Object Relational Mappers, but this is not the place. One thing though - various people have objections against using domain model and object relational mapping for the wrong reasons. Be skeptical about objections concerning security, performance and SQL injection. More on that another time.
Much has been said about domain models. In recent years, the most influential books on the topic have been Martin Fowler's Patterns of Enterprise Application Architecture and Eric Evans' Domain Driven Design.
Centralized or delegated control
If you take a look at sequence diagrams of logic designed as the two procedural patterns, transaction script and table module, compared to the object oriented ones, active record and domain model, you will see two quite different information flows. The procedural ones uses a centralized control style, whereas the object oriented ones use a delegated one.
The logic for transaction script or table module is controlled from one location. The script knows which steps the transaction needs to take to perform the task, and asks appropriate helper classes to solve each step. A sequence diagram will show you information going into helper classes, often with single parameters or a form of DTOs, and back again, then into new classes, and so on. You have a central point of control.
Active record and domain model works in a different way. Here the responsibility is typically delegated to one or more objects, which again delegates responsibility to other objects. A sequence diagram will show you a flow going into an object, then delegated into other objects, and so on, instead of going back and forth from the central location. In this way you have a delegated form of control.
I think the procedural, or centralized control style, is more common in .NET than in Java. The main reason is the support and focus that Microsoft has had.A research paper was published in IEEE Transactions on Software Engineering, called "Evaluating the Effect of a Delegated versus Centralized Control Style on the Maintainability of Object-Oriented Software", where about 150 senior, intermediate and junior developers, including a number of students, participated. The developers had to make various changes in both a delegated and a centralized design. The results:
"The results show that the most skilled developers, in particular, the senior consultants, require less time to maintain software with a delegated control style than with a centralized control style. However, more novice developers, in particular, the undergraduate students and junior consultants, have serious problems understanding a delegated control style, and perform far better with a centralized control style".
And then concluding:
"Thus, the maintainability of object-oriented software depends, to a large extent, on the skill of the developers who are going to maintain it. These results may have serious implications for object-oriented development in an industrial context: Having senior consultants design object-oriented systems may eventually pose difficulties unless they make an effort to keep the designs simple, as the cognitive complexity of 'expert' designs might be unmanageable for less skilled maintainers."
I think the conclusions with the delegated style of control also has a lot to do with familiarity. Since many .NET developers, including senior ones as well, have limited experience with this, imposing a delegated style can take some time getting used to.
Have I covered everything then?
Since I put the heading in here, I'm sure you already know the answer to the question. There is one pattern I haven't mentioned yet, or an anti-pattern anyway. And a common one, that is.
I've mentioned several times in this post how the .NET community often have had a procedural focus. I think that is the reason why this pattern seem to be so popular in .NET-land. Let's have a closer look at what I'm talking about.
Anemic Domain Model
The Anemic Domain Model looks like a domain model, has a rich structure of objects, but there's almost no behavior in the objects. The logic is typically controlled via a transaction script, with the model being simply data containers.
The examples are Hello World in complexity, but hopefully still captures the main difference between this and a regular domain model. As you can see, the logic has mostly been moved out of the domain model, and into a different class. It's quite common that these are referred to as xxHelper, xxManager, xxHandler, or other general terms.
This (anti-)pattern can be easier to understand for those struggling with delegated control, but this is a benefit with a cost. Other benefits is that you can brag about having a domain model to your friends (which you don't), and any other benefits you get with transaction script. The problem is that you get the same problems as with transaction script - a challenge with complex code, duplication of code and logic that is harder to unit test. In addition you get the complexity of using an OR/M on top of that.
None of these patterns is never right. That's part of the fun of software development. You need to take a close look at what you need to build and then take an informed guess. Take into account the positive and negative sides, and when you have made the choice - make real effort to diminish the effects of the negative sides.