Suppose we are building yet-another-order-basket-application. We have the requirement for adding items to an order, otherwise the business of our entire company falls down and we are losing tons of money (while not overdramatizing things).
The code I see popping up over and over again looks something like this:
public class DataDrivenService { private Database _database; public SomeService() { // Setup database member variable } public void AddItem(Int64 orderId, Int64 productId, Int32 quantity) { String sql = " INSERT INTO ORDERITEMS " + " VALUES(@OrderId, @ProductId, @Quantity) "; // Code for setting SQL parameters _database.ExecuteNonQuery(sql, parameters); } }
Besides the fact that it uses some crappy data access framework like the Data Access Application Block of the Enterprise Library, there are some things that are really wrong with this code.
For starters, this code completely neglects the concepts of our business domain. Its is all about database connections and executing SQL statements.
Secondly, it violates the Single Responsibility Principle. This code clearly has the concept of SQL and the database schema. As mentioned earlier, it also hides the concept of adding items to an order. Both of these concerns change for different reasons.
Third, this code is simply not testable without incorporating the database. It probably has not unit or integration tests at all. This is also called legacy code.
What’s a better approach then?
public class Order { private IList<Item> _items = new List<Item>(); public void AddItem(Product product, Int32 quantity) { _items.Add(new Item(product, quantity)); } } public class SomeAppService { private IRepository<Order> _orderRepository; private IRepository<Product> _productRepository; ... public void AddItem(Int64 orderId, Int64 productId, Int32 quantity) { Order order = _orderRepository.Get(orderId); Product product = _productRepository.Get(productId); order.AddItem(product, quantity); Repository<Order>.SaveOrUpdate(order); } }
This code nicely separates the two concerns discussed earlier. If the database schema changes, then the database code inside the repositories needs to be changed while the business logic is left untouched. What I probably like the most about this orthogonal approach, is that it enables me to forget about the database until the last responsible moment.
I’ve said this quite a number of times already: Databases are just a detail. There just another piece of software to efficiently store data on disk. That’s all! Why some people want databases to be at the center of their application is simply beyond my intellectual capacities.
As always, there is a time and place for data-driven applications. I just don’t buy that it is well suited for applications with more complexity than vanilla ice cream. This means that it is almost always the wrong choice for any long-lived application.
Just to be nit picky …
That’s not a [Domain Service] its an [Application Service]
Also why not introduce a CartItem value object instead of multiple parameters.
public void AddItem(CartItem _Item)
Ok I am done. Good post 🙂
Yes, you’re right. It would probably be an application service taking a CartItem DTO, but I didn’t want to overload the simplest example I could think of. 😉
So in terms of performance, the first example will execute with one round-trip to the database. The second example will need three. I hope the site this is supporting never needs to scale to a decent number of users because it won’t.
Although not shown in the first example (for simplicity), there would probably be a check for a valid order ID and a valid product ID (never trust input from the user). Then you have three DB calls as well.
It really doesn’t need a parameter object (DTOs and VOs have different purposes) for that few of parameters.
And Greg, it actually might NOT make more than one trip to the db. That is another advantage (beauty) of this.
“They’re just another piece of software to efficiently store data on disk. That’s all!”
SQL is also (the best way I can think of) to perform deep subqueries to extract, cache, and visualize magic nuggets of live data from your well-indexed tablespaces.
It’s not just for “crud”.
“SQL is also (the best way I can think of) to perform deep subqueries to extract, cache, and visualize magic nuggets of live data from your well-indexed tablespaces.”
SQL is not just for RDBMS.
“It’s not just for “crud”.”
That’s what he said.
Nice post, keep up the good work.
Thx for the kind words.