0

I'm looking for a design pattern that might work for this class I am working with. This main class is an entity using Domain Driven Design.

   public class TimeCard() : ITimeCardHeader
    {
        public int TimeCardHeaderID { get; pivate set; }
        public int ContractorID { get; internal set; }
        public System.DateTime Date { get; internal set; }
        public StateEnum State { get; protected internal set; }
        public System.DateTime CreatedDate { get; internal set; }

        public Update( ITimeCardHeader header)
        {
         //validation logic.
         //assign values  e.g. This.ContractorID = header.ContractorID
         // create and send domain event "Time card change" 
         }
    }

I would like to ask Questions(functions) of the timeCard object to determine if it could be edited. However, the business rule of if a timecard can be edited really is based on who you are.

So my Idea is to ask questions via an interface

interface ITimeCardCreater{
 bool CanEditTimeCards{get;}
 int? ContractorId {get;}
 }

Then I could have higher level classes Create a user then add methods to my TimeCard like.

    timecard.CanEdit(ITimeCardCreator);

My Question is where should that type of logic live. I had it directly on the Timecard object but I now think that it should be a class unto itself as it's getting large.

Edit as suggested by king-side-slide Rename interface to ITimeCardCreater.

Question: is there a design pattern that removes logic from a domain model that is exclusively used by that model to answer questions against it.

  • 1
    maybe an inquisitor design pattern? – WizardHammer Jan 16 at 1:20
  • 1
    In general, that should be avoided. It breaks OO invariance, and invites race conditions. – Telastyn Jan 16 at 1:44
  • You might also take a look at Aspect Oriented Programming (AOP) - but for C#, you'd need a third party library to support that. – Bernhard Hiller Jan 16 at 8:51
  • You describe the Timesheet class here as generated by DDD but it looks like an anemic domain model to me. It seems like your question is really about how to build a domain model on top of a property bag. This article may be helpful. – JimmyJames Jan 21 at 16:18
  • @JimmyJames Model in this example is simplified for the purpose of the Question. The real model has more methods. In fact, that is the root of the question. There is so much logic in the class and shared logic I would like to encapsulate many private methods into a separate class such that I can test easier. – WizardHammer Jan 22 at 0:39
3

I would like to ask Questions(functions) of the timeCard object to determine if it could be edited.

The more common approach is Tell Don't Ask. The logic that decides whether or not to change some data belongs in the object that owns the data that is going to change.

When the decision depends on data the changing object doesn't own, normally you either pass in the data that it needs, or you pass in the capability to ask the question.

So you would be looking at something like:

TimeCard::Update( ITimeCardHeader header, IUser user) {
    if (user.CanEditTimeCards()) {
        // ...
    } else {
        // ...
    }
}

You'll notice here that we are telling timecard, but timecard is asking user. That's OK (in this example) because user isn't changing, it's just contributing data. It's OK to give the object that is changing an (immutable) copy of your data.

There is a pattern called CQRS which takes this idea a step further - instead of passing an entity to be queried, you just pass its "read model" - a copy of its current state that supports questions but not changes.

"Domain Services" is a term used to describe the stateless "things" that we pass to entities so that they can access some capability outside of their own specialty.

  • This is the right direction and I have implemented this behavior already. My problem is there are about 8 different methods that change the internal state of the timecard. each need to confirm if that user can edit. so i have a private method CanUserEditThisTimeCard(Iuser user). this method has quite a bit of logic, and I would like to just mock the response so that unit testing is simpler. Can this logic exist outside of the Time Card and if so is there a best practice. – WizardHammer Jan 16 at 19:10
1

What I see here is a missing concept. You are attempting to merge the data between two different objects in order to enforce rules and affect behavior. Isn't this precisely what an entity is supposed to do?

DDD seeks to model a system according to behavior. Often this means partitioning our data vertically according to our behavior. It also means User is usually a poor choice for a domain concept because it denotes very little behavior (uses what?) and encompasses too much knowledge. What do your users do? Buy things? Buyer. Post articles? Poster. You get it. To me it is confusing that a User would interact with a TimeCard.

Let us simply introduce a new entity, Employee, to explicitly encapsulate the behavior we would like our system to exhibit. Now where User may govern how Username and Password can change, our Employee aggregate is responsible for managing its TimeCard (which can be refactored into a value object). Now the data necessary to know whether a TimeCard can change is together with the data we would like to change!

Taking a higher-level view of the above question/answer, I'd like to a moment here to impress upon you the importance of revisiting the design of a system when a problem like the above surfaces. So often I field questions formulated like, "what is the pattern" or "where is that one missing detail" "that can solve {my current problem}". The issue is that there isn't one! The problem hasn't been caused by a missing pattern or detail. The fundamentals of of SOLID design and DDD aren't changing. The vast majority of issues are a result of the design. That is, a design is "chosen" and when a problem arises we are left searching for a workaround that we call a solution. In reality a design is discovered! A domain model is a model of the rules, therefore, if the rules change the design must often change!

EDIT

I'd also like to make note, because many of the other answers kind of boil down to this (under different names), that relying on too many domain services is often a sure sign of an anemic model. A domain service is best-implemented as a place to coordinate logic/interaction between entities, not enforce logic itself. The classic example is that of the interaction between Account and ATM. In order to Withdraw money from an ATM there are two conditions/actions that must be satisfied: the Account must have the funds available, and the ATM must have the cash on hand. Because these two rules are disparate we need to introduce a service to provide the coordination:

// inside service Withdraw method
account.Debit( amount ); // may throw InsuficientFunds

atm.Dispense( amount ); // may throw FundsNotAvailable

The above service still isn't "asking" for anything, it is simply coordinating an action that occurs between entities.

  • Made an update to iTimeCardCreator based on suggestion. Good Point. – WizardHammer Jan 22 at 17:59
  • "You are attempting to merge the data between two different objects in order to enforce rules and affect behavior. Isn't this precisely what an entity is supposed to do?" -- Yes, though I would say I'm attempting to affect the behavior a timecard based on the user interacting with it. (User is always unchanged). – WizardHammer Jan 22 at 18:16
  • Confirming YES is correct to the hypothetical Question. "Isn't this precisely what an entity is supposed to do?" @king-side-slide – WizardHammer Jan 22 at 18:17
  • @WizardHammer I don't want to belabor this because I think you understand, but the point I am trying to make is that DDD (and OOP in general) seeks to model behavior with data, not around data. The phrase "[to] affect the behavior of a timecard based on the user interacting with it" is separating the data (user) from the behavior (timecard). What we want to do here is explicitly model the concept of "user interacting with timecard" such that where we once had two entities (User and TimeCard) we now have one (Employee). When this is not possible like for an Account and ATM ... – king-side-slide Jan 22 at 20:18
  • @WizardHammer We want to then break the behavior into a process that can be managed by a domain service. So where we once had a single unit of behavior we now have two units that are coordinated by our service. Often employing a factory method makes most sense regarding permissions. Something like User.RequestTimeCardEdit( data ) -> PendingTimeCardEdit -> TimeCard.Edit( pendingEdit ) would be a suitable solution in terms of process. The user must first obtain a pending edit (permissions are checked here) then that pendingEdit can be passed to the TimeCard to yield mutation. – king-side-slide Jan 22 at 20:25
0

Code should have one reason to change

Authorisation and Time Cards sound like two different ideas. So why are you attempting to put them in the same object? It sounds as if you are trying to make the Time Card aware of the surrounding business rules.

Similarly the User should be blissfully unaware of Time Cards themselves. It should be aware of having a certain Privilege, but whether or not that permits editing a time card is for the business logic to decide.

The scenario

This system has Users, and it has Time Cards, and it has Business Logic. The Business Logic is responsible for determining if a requested action, "Edit this Time Card" can be done by a given user, performing the action if applicable, and report the outcome.

Now if the programming language supports functions at the package/namespace levels that is all this piece of Business Logic needs to be: void EditTimeCard(User, TimeCard, informationRelevantToEditing).

If the language does not use the Command pattern: class EditTimeCard { void execute(User, TimeCard, informationRelevantToEditing); } or class EditTimeCard { EditTimeCard(User, TimeCard, informationRelevantToEditing); void execute(); }

The Time Card can still be a mutating object. There is no reason why it cannot behave that way. Though there are plenty of reasons to not share mutating objects. It might make more sense to treat Time Cards as Values (ie. not shared), or as Read-Only (share-away) and then submit these to another Entity to persist the changes. A reasonable entity in a Time Card system might be a Time Card Series, or an Employee Time Schedule.

  • "So why are you attempting to put them in the same object?" @Kain0_0 I'm only dealing with Authorisation as it relates to a timecard. I only want persons authorized to update a timecard to update it. Yes i am trying to make TimeCard aware of bussness rules. My understanding is that is a core premise of Domain Driven Design. – WizardHammer Jan 16 at 19:17
  • @WizardHammer That is a road to pain. If you insist on dealing with a one to one correspondence between Domain Entity as conceived and the Type as represented in a language, use the decorator pattern, or place the handling of the Time Card Value into a TimeCardValue, and have TimeCard responsible for the handling of the TimeCardValue in the context of the business rules. At least then when the business rules change (they will) you can manage them separately from how the value itself is handled. – Kain0_0 Jan 16 at 22:38
0

This could be done with a decorator which checks authorization. The benefit is that the domain logic is separate from the authorization logic.

public class AuthorizationFooBarDecorator : IFooBar
{
    private readonly IFooBar m_decoratee;
    private readonly IUser m_user;

    public AuthorizationFooBarDecorator(IFooBar decoratee, IUser user)
    {
        m_decoratee = decoratee;
        m_user = user;
    }

    // example of a property on the interface which this decorator is decorating
    public double IFooBar.Weight
    {
        get { return m_decoratee.Weight; }
        set
        {
            if (!m_user.CanEditFooBar) throw new AuthorizationException();
            IFooBar.Weight = value;
        }
    }
}

The decorator and the decoree expose the same interface (by the nature of the decorator pattern). The client code has a reference to the interface without knowing if it's talking to a decorator or to the decoree.

[Inspired by this write-up about aspect-oriented programming.]

0

The Strategy Pattern,

A TimeCard should be the gateway to changing its own internal state. The Strategy pattern allows the Timecard to own the validation logic methods that need to be asked without having to implement them. While Simler to the decorator pattern.

In this case, timeCard should never be altered with outgoing through validation, and logging.

Also Implementing as a Strategy means that the strategy and TimeCard methods can be tested separately.

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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