IndependentDomainModel

Context
In traditional web applications, The domain logic depends heavily on a relational database. In many systems the domain logic is not separated from the database at all, but is implemented purely using SQL statements. This design works reasonably well with these systems. However, this is a terrible design for the domain logic of a smart client system. Here are some reasons why.
- Domain logic can not be executed on the client. Attempting to execute domain logic on the client leads directly
to code duplication, the bane of good software. - The system cannot support disconnected use because access to the domain logic is dependent on access to the network. This wipes out one of the common motivations to build a smart client.
- It is unlikely that the system will realize better responsiveness than a web application because users must post frequent requests to the server to complete tasks.
- In a traditional domain layer, there is no straightforward way to support bidirectional communication. Communication from a “lower” layer to a “higher” one must happen through some indirect mechanism. This is okay for a few special cases, but not when bidirectional communication is a vital part of the system.
The one exception to this is a system where the client processes connects directly to a database. This is a problematic design for several reasons:
- It isn’t scalable to large number of users, to acheive scalability you need (at least the option) to put a transport layer between your client processes and the application database, using a technology such as web services or tcp sockets.
- Allowing users to connect directly to the databases raises the security risks considerably.
- The system will probably never be accessible from the internet due to security concerns.
- The system will not support bidirectional communication
- The system will not support disconnected use.
Therefore, if you are going to implement a system with a domain layer that depends on the database, the only advantage to using a smart client is the potential to create a richer user interface. As I said before, the benefits here are largely theoretical, and at the moment, the implementation is quite risky. The risk being that your user experience will be less than what you could have acheived with the browser in the same time. In order to acheive the other, more compelling benefits of a smart client implementation you need to be able to easily access your domain logic from any part of the system. This is the only way to provide rich, domain specific behavior in your application without hampering your distribution and integration options. Furthermore, you do not want to create duplicate domain code between the client and server.
Problem
How can the application’s domain logic be implemented so that it is available anywhere in the distributed system?
Solution
Domain Model Vs. Domain Layer
The traditional solution to implementing domain logic in object-oriented systems is the Domain Layer. A domain layer is so-called because the layer is accessed by calls from “above” and forwards calls to other layers “below”. Often, there is a Service Layer [Fowler] containing a set of methods that can be called in order to update the database. This is a problem for smart client applications because it causes the domain logic to be inaccessible from certain parts of the system. The biggest problem is with the user interface, but the inability to execute domain logic freely can also affect the transport and data access layers. This leads to domain specific logic being scattered and duplicated around the system in ad hoc ways, eroding the centralization of domain logic.
An independent domain model is a collection of classes, specific to the problem domain, that model and implement domain functionality without saying anything about the infrastructure services or how they are accessed. An independent domain model does not even treat infrastructure services as abstractions. Instead it remains entirely ignorant of infrastructure.
Realizing Dynamic Control Flow - Focus on the verbs.
In a smart client application, the control flow might vary at runtime. For example, a chat message might be routed to an individual if that person is available, but if they are not, it might be stored in a database for later retrieval. If the recipient has set particular options, it might be forwarded to their cell phone. In order to support dynamic control flows of this sort, an Independent Domain Model takes a hands-off approach to control flow. Instead, an Independent Domain Model depends on the Command pattern [GOF] and models system actions using classes rather than methods. The command pattern is exceptionally well suited to distributed systems because, unlike method calls, commands can be logged, queued, undone, and routed to various parties with ease. Most importantly for the domain model, using the command pattern focuses attention on user actions. The most important domain objects in business software systems do not represent nouns such as Customers, Employees, etc… The most important objects are verbs, such as GetPendingOrdersForCustomer, or DeactivateEmployee. Rarely are developers asked to create in memory simulations of the interactions between customers and employees. Normally, we are asked to be able to do a particular action related to them. Your client might need to AddOrderForCustomer from the web, for example.
You’re users actions are extremely important to them, therefore, they should also be important to developers.
Therefore, treat user actions as your primary domain objects, and model them first. After you have a clear idea of what commands your system will support, then you can add “Core” objects that represent the nouns in your systems. When you are done, you will have two sets of objects:
- Commands - these are objects that represent the verbs in your system. (ex GetOrdersForCustomer)
- Entities - these are objects that represent the nouns in your system. (ex Customer Order)
You will not know anything about how commands are executed, and how their results are received. You will not know anything about the persistence system, the user interface, or the transport mechanism. All of this behavior belongs outside of the domain model.
Domain Object Behaviors
In general, the behaviors that should be implemented by domain objects are those that never require remote calls. Here are some examples:
- Validation
- Comparison
- Pretty Printing
- Serialization and Deserialization (I’m on the fence about that one)
- Special Algorithms or calculations that are unique to the problem domain.
If a behavior requires the invocation of a remote call, the invocation should be modeled as a command, and your domain object will need some type of delegate to be passed to it, because it does not know about any layers that it can call on. In general, this is a technique to be used sparingly because it adds significant complexity. Also, it can lead to hidden, transparent remote calls that can hurt performance if developers are not aware of them. Common operations such as “Create” “Update” “Delete” should be modeled as commands that contain core objects rather than methods on core objects.
Advantages
Unlike a Domain Layer, which can only run in certain, server-side processes, a domain layer can be deployed to any or all processes in a distributed system. This makes it easy to add domain behavior or decisions to this one component rather than scattering it about the system. For example, you can not really build a user interface without utilizing domain logic of some sort. When you can not use your domain model in the same process as the ui, you end up duplicating logic to the client. An IndependantDomainModel lets you add domain functionality to the domain layer and call it from any place that needs it. So you can validate domain objects in the client process to give users real-time feedback on their input. Then you can validate the objects again on the server-side because you can not always be certain that information coming over the network is acceptable. A domain layer forces you to choose one type of validation over the other, or to duplicate code.
An Independant Domain Model can help you realize “the dream” of having well-organized, centralized, domain logic in your distributed system. But at the same time it leaves open the question of how you actually call on services and infrastructure. The other patterns in this section describe a framework that allow for loosely coupled, distributed service requests that operate synchronously or asynchronously.
Example Code
Here is some code that gives you the gist of the important players in an IndependentDomainModel, without going to deep into implementation details. Note that you would need an additional layer to provide services and access them.
using DomainModel; public abstract class Main { public static void Main() { Main main = new MainImpWhichDoesntExist(); GetCustomer command = MakeGetCustomerCommand(\”brian\”); CommandResult result = ExecCommandSynchronous(command); Customer resultData = (Customer)result.Data; System.Console.Out.Write(resultData.PrettyPrint); } private abstract CommandResult ExecCommandSynchronous(Command command); private abstract MakeCustomerCommand(string userID); } namespace DomainModel { public interface Command { bool IsValid(); bool Equals(Command other); string PrettyPrint(); } public interface CommandResult { ResultCode ResultCode{get;} string Msg{get;} object Data{get;} } public interface CoreObject { bool IsValid(); bool Equals(CoreObject other); string PrettyPrint(); } public interface GetCustomer :Command { string UserID{get;} } public interface Customer :CoreObject { string UserID{get;} Address ShippingAddress{get;} PhoneNumber HomePhoneNumber{get;} PhoneNumber CellPhoneNumber{get;} } }