2

Situation: I am using LinqToSql (could be considered irrelevant) for my 'persistence layer' and am trying to work out some architectural concerns I have about where certain questionably business-related logic should go.

The scenario: the user of the application creates a new order. When they do so, a collection of product keys needs to be associated with that order.

My first attempt was to put all of this jazz in an OrderService class. Then, I attempted to incorporate it with my DataContext by using a partial method:

partial void InsertOrder(Order instance)
{
        var productKeys = this.ProductKeys
            .Where(x => x.DeleteDate == null && x.Order == null && !x.Issued)
            .Take(instance.NumberOfProductKeys);

        if (productKeys.Count() != instance.NumberOfProductKeys)
            throw new NotSupportedException("There needs to be more product keys in the database.");

        instance.ProductKeys.AddRange(productKeys);

        this.ExecuteDynamicInsert(instance);
}

Disregarding the fact that this doesn't work as intended (the product keys are never actually associated with the order), I feel that this is stripping logic away from my business domain and pushing it to my 'persistence layer'. I also thought about putting it in a OrderService class, but felt that it too just took away logic from the domain entities and resulted in a transaction script. Introducing an Order Factory just circumvents the issue: the data and the logic are once again separated.

So my question is this: to avoid an anemic domain model and to hopefully have order doing something besides being a glorified data structure, is there a proper way of integrating this logic into my domain model?

The best solution that I have come up with yet is to put the logic to the Order class and verify that it was done in the validation hook:

public partial class Order
{   
    public void AssociateWithProductKeys(WIIIPDataContext context)
    {
            var productKeys = context.ProductKeys
                .Where(x => x.DeleteDate == null && !x.Issued && x.Order == null && x.ProductType == ProductType)
                .Take(NumberOfProductKeys);

            if (productKeys.Count() != NumberOfProductKeys)
                throw new ValidationException("There needs to be more product keys in the database for product type: " + ProductType.Description);

            ProductKeys.AddRange(productKeys);

            foreach (ProductKey productKey in ProductKeys)
            {
                productKey.Issued = true;
            }
    }

    partial void OnValidate(ChangeAction action)
    {
        if (action == ChangeAction.Insert)
        {
            if (ProductType.Id == 3 && ProductKeys.Count() != 1)
                throw new ValidationException("Attempted to associated more than 1 product key with a CD version.");

            if (ProductKeys.Count() != NumberOfProductKeys)
                throw new ValidationException("Number of product keys doesn't match expected value");
        }
    }
}

Consuming code would look like so:

// The Unit of Work
using (var context = new MyDataContext())
{
    ...
    order.AssociateWithProductKeys(context);
    context.Orders.InsertOnSubmit(order);
    context.SubmitChanges();
}

== Update 3/29/2012 ==

I have adopted a command/query pattern when using LinqToSQL (and Web Forms) and no longer use the entities created by LinqToSql DataContext as anything more than a mapping to my data store. All my rules and what not go into into the command objects, making them, in a way, the real core of the application.

Merritt
  • 2,333
  • 21
  • 23
  • I don't understand something. Why would any layer, logic or data, try to find products in the DB to associate with an order before persisting? Shouldn't an order already have products associated with it? I would think that the client would associate products with orders, the logic class would validate for any rule violations, and the DAL would simply map the domain entity to the DB entity/table and persist it. No? – Bob Horn Mar 14 '12 at 00:19
  • The client is not responsible for associating product keys with the order. When the client creates an order, they specify the number of keys they need. The system is responsible for assigning those keys. That's the heart of the matter: where does that assignment logic go and what is its true nature: business or otherwise? – Merritt Mar 14 '12 at 00:28
  • Ah, you're talking about just the assignment of keys. Okay. So the client picks the number of product keys, but not which actual products? If the client picks a product for an order, the key should already be there in the entity. It shouldn't be available for the client to see, but it should be there nonetheless. – Bob Horn Mar 14 '12 at 00:34
  • The client is only responsible for providing a number of product keys and a type of product (online update, online full, cd copy). There is a repository of product keys to use, and each time a order needs to be created there needs to be a check against that repository to make sure there are enough available. If they are, the system associates those product keys with the order. The product key here is not an Id field, but a authentication mechanism for activating a desktop application. – Merritt Mar 14 '12 at 00:41

2 Answers2

0

The Client passes the order to Business Logic Layer (BLL). BLL calls DAL method to get n product keys. The DAL implements no logic, it simply gets n keys. The BLL then reacts to what the DAL provided. If there are enough keys, the BLL associates those keys with the order and returns. If there are not enough keys, then the BLL does not associate keys with an order and throws the exception. The client then provides the right UI message to the user, based on what the logic layer returned.

I agree with your own assessment that logic should not exist in the DAL. This means that DAL method could possibly be reused from a different use case scenario, and we don't want the DAL deciding anything, because business rules may change and new use cases may emerge.

Does that help?

Bob Horn
  • 33,387
  • 34
  • 113
  • 219
  • You are stating what I have stated, just in different terminology. I am not sure what answer or suggestion you are making. – Merritt Mar 14 '12 at 19:06
0

I am using LinqToSql (could be considered irrelevant)

I wouldn't consider that irrelevant at all.

Disregarding the discussion of whether LINQ-to-SQL is a 'real' ORM, I find that all ORMs (and particular LINQ-to-SQL) are very prescriptive in the way that they want you to use them. Stick to their preferred pattern and everything is straightforward. Structure your code as you choose and you risk heading for a world of pain.

In this instance, LINQ-to-SQL works best when it comprises both the data access and logic (tightly coupled). This is a terrible practice once you have anything other than a very small project and will lead to debugging nightmares, but if you try to break data access and logic apart you'll hit some common problems.

Microsoft's recommendation is that a DataContext should be a short-lived object used for a unit of work. This approach doesn't work well because of the 'attached' / 'detached' model that LINQ-to-SQL uses. There are dozens of websites proposing the 'detach through serialization' hack which is, in my opinion, awful.

In addition, the ORM model lends itself to data objects rather than full-featured encapsulated objects. The DataContext needs to maintain the relationships between objects, so having them be responsible for their themselves will often lead to later headaches.

While I enjoy LINQ-to-SQL and have used it on a number of projects I can't recommend a great pattern, and would actively recommend against any object-oriented pattern at the data level. Forget what you know and instead try to write an easy to maintain data layer. In my experience, separating your logic and data access based on strict design rules will lead to significant problems later on with this toolkit.

Kirk Broadhurst
  • 27,836
  • 16
  • 104
  • 169
  • I am tempted to vent about how much this answer bothers me (I deleted my previous comment), but will instead say thank you for offering your opinions on the architectural ramifications of using LinToSql. However, its not really addressing my question directly, is it? If it helps, pretend instead of using a DataContext you are using a Repository pattern. How would your code look then? – Merritt Mar 14 '12 at 19:04
  • I guess why I was aggravated was that my question was about revitalizing my business objects and really had little to do with persistence. I find myself in a pattern of writing AggregateRootXService and putting all my logic in there, and am just sick of it – Merritt Mar 14 '12 at 19:18
  • @Merritt I understand your frustration and I don't like answers that are mere comments rather than answering the question, but as you had few answers I thought I'd give some feedback. My answer to your "Where should my code be..." question, in shortest possible terms, is "Right next to `new DataContext()`". The longer answer is that when using LINQ-to-SQL, any design considerations are secondary to code usability. – Kirk Broadhurst Mar 20 '12 at 08:06