Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

In our ASP.NET MVC application, we try really hard to separate concerns to each layer (we use DDD). Nevertheless, it seems that we can't avoid having at least some business logic in DAL.

For instance, there are named objects, where business requirement is that each name must be unique (but the name isn't a key). If there is a request to create new named object, the service checks whether some object with that name exists in repository, if it doesn't, factory creates new object and repository persists it (we use EF for repositories and stored procedures for query objects).

The problem is with race conditions - if two requests come concurrently (it's unlikely, but possible), both requests want to create new object under the same name, in this case both checks come out negative and two objects with the same name would be created. So far, we handle that with constrains in SQL database, so if something like this happens, an exception is thrown in repository - but this approach is simply wrong, since such requirement is strictly business requirement and it shouldn't bubble to persistence layer; if we change such requirement, e.g. that names become case sensitive, such constrain would also have to be changed, etc.).

How to implement such scenario correctly? Thanks.

share|improve this question

migrated from stackoverflow.com Mar 14 at 19:49

This question came from our site for professional and enthusiast programmers.

    
Moving these logic to service and using transaction scopes should do the trick. –  saravanan Mar 15 at 6:53
    
Not sure why this question was migrated from StackOverflow. I think it's more appropriate there. –  Josh Kodroff Mar 15 at 18:38
2  
This question does indeed belong here on Programmers. Conceptual questions about things such as design are on-topic here. Stack Overflow tends to focus on implementation details and tools to support software development. –  Thomas Owens Mar 15 at 20:54
    
@JoshKodroff: Read the comment below the original question on Stack Overflow. –  Robert Harvey Mar 15 at 20:54

3 Answers 3

up vote 6 down vote accepted

BTW Agree with @Robert Harvey.

There's a subtle but problematic shift in your question -- the original question is "how to isolate business logic" but then you switch over to isolating "business requirements".

Business requirements will permeate your system. Of necessity. During the data modelling effort, that's going to be driven by the data that the business has identified, and constraints on the data. Your data model will always reflect the business requirements. For example, the business may require that you capture First, Middle and Last names for customers. So your database will contain those fields, and there's just no way to isolate that from the business requirements.

Business logic, on the other hand, is a different beast. That is, business logic is little snippets of code like the calculation for a customer's monthly fee. Or a predicate, like whether or not a customer is eligible for some promotion. If your DAL has those calculations built into it, then you've crossed a line.

Providing a mechanism like uniqueness or concurrency is definitely going to come from a business requirement, but it's not business logic. The business doesn't care how you provide concurrency and uniqueness, as long as you do. You could say it supports business logic, but isn't specified by the business.

On the other hand, a calculation of the user's annual contribution percentage, or whatever, is code the business specifies directly, and is something you want to isolate for the sake of testing, validation, flexibility, etc.

share|improve this answer
    
That's very clear, thanks a lot. I didn't see the forest for the trees. –  Robert Goldwein Mar 14 at 21:42

So far, we handle that with constrains in SQL database, so if something like this happens, an exception is thrown in repository - but this approach is simply wrong, since such requirement is strictly business requirement and it shouldn't bubble to persistence layer.

You can make that argument if you like, but your relational database already has the necessary indexing and concurrency mechanisms to make it very convenient and reliable to do it there.

If the idea of detecting duplicates in a database makes you uncomfortable, then think of uniqueness as an attribute of the data. Enforcing such an attribute is a perfectly valid thing to do in a database. You wouldn't argue that the type of a data field (i.e. text, float, currency) is a business rule, would you?

share|improve this answer
    
Thanks, I totally agree, the reason why I feel uncomfortable is that how I see DAL, it should be completely business agnostic. Checking for uniqueness in IDs is perfectly valid, since it assures DB consistency, but my scenario isn't about DB consistency, it's about business requirements (fulfilled by DAL). –  Robert Goldwein Mar 14 at 19:43
    
The purpose of a database is to store information about a business domain. Were that not the case, we could simply put everything into a single table. –  Robert Harvey Mar 14 at 19:47
    
I have no idea why the question was migrated here, an argument "Without a language tag or some example code, it's not going to get much attention here." applies for most SO posts with DDD tag. –  Robert Goldwein Mar 14 at 20:02
    
@RobertGoldwein 'it should be completely business agnostic' taken to an extreme that would require you to name all your tables and columns entirely unrelated to the business. e.g. TableA.ColumnA1, TableA.ColumnA2, TableA.ColumnA3, etc. No sensible database design should ignore the business aspect –  Clara Onager Oct 15 at 13:29

I would do it like this:

public class NamedObjectRepostiory
{
    private IDataStore _dataStore; // this is just a wrapper around Entity Framework to make it more testable

    public NamedObjectRepostiory(IDataStore dataStore)
    {
        _dataStore = dataStore;
    }

    public void Save(NamedObject namedObject)
    {
        // This is basically a transaction scope/DbContext instance.  You can inject the IDataSession
        // rather than the IDataStore if you want scope to be managed by your DI
        // container, e.g. session per HTTP request, which is a common pattern
        using (var session = _dataStore.GetSession()) {
            if (session.Set<NamedObject>().Any(x => x.Name == namedObject.Name))
                throw new Exception("Cannot have 2 NamedObjects with the same name.");

            // save data...
        }
    }
}

No unique constraint needed (assuming that all writes go through the application, which they pretty much have to if you're doing DDD).

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.