Where do long running, stateful 'services' fit in DDD?
Asked Answered
R

2

6

In more industry or automation related applications (that mostly heavily rely on external components which they have to manage), you'll often end up with the case that the domain contains models which are more than just abstractions from the real problem, but also representations of and pointers to something that physically exist outside of the domain.

For example, take this domain entity that represents a network device:

public class NetworkDevice {
   public IPAddress IpAddress { get; set; }
}

Instead of just storing or validating such entities or taking actions upon entity changes, an application may need to manage external components based upon their representations inside the domain. Now, is DDD even suitable for such cases? Are those managers domain services?

Eric Evans describes in his famous blue book that a domain service needs to be a stateless model implementing methods taken from the ubiquitos language to fullfil a request that the entity, or a repository cannot handle by itself. But what if a service needs to be stateful?

A simple example: An application needs to monitor configured IP devices within the network in order to notify other application inside the domain about state events. If a IP device gets registered inside the application (stored inside a database, for example), a "ping-service" gets notified and starts to monitor the device.

public class PingMonitor : IDisposable,
   IHandle<DeviceRegisteredEvent>,
   IHandle<DeviceRemovedEvent> 
{

    public List<NetworkDevice> _devices = new List<NetworkDevice>();

    public void Handle(DeviceRegisteredEvent @event) {
        _devices.Add(@event.Device);
    }

    public void Handle(DeviceRemovedEvent @event) {
        _devices.Remove(@event.Device);
    }

    public void PingWorker() {
        foreach(var device in _devices) {
            var status = Ping(device.IpAddress);
            if(status != statusBefore)
                DomainEvents.Raise<DeviceStateEvent>(new DeviceStateEvent(device, status));
        }
    }

}

Other components could then handle those status events and e.g. stop talking to the device via other protocols if the device goes offline.

Now, what are those components? At first I thought they are domain services because they serve a certain requirement of the domain. However, they are stateful as well as do not specifically represent the ubiquitos language (the task of a ping-service is to ping a domain entity and report it's status, however the ping-service does not implement a method that would clients allow to ping a device).

Are they application services? Where do such components fit inside the DDD pattern?

Reyreyes answered 6/3, 2014 at 7:58 Comment(2)
I don't see why PingMonitor is stateful. What state does it hold?Rictus
@Hippoom PingMonitor stores and watches all network devices that have been registered inside the application during the whole application lifetime and therefore runs in a singleton lifetime scope.Reyreyes
R
1

I once implemented a similar function, hope it helps :)

Our organization owns an online-payment handling application. Once a customer finishes payment, the online-payment provider sends use a notification indicating success or failure. Somtimes network failures occur, the notification may never reach our application. hence, here comes disgruntled customers. So an auto-checking mechanism is needed.

An application runner is responsible for keeping the check running:

public class CheckingBatch {
    private TransactionChecker transactionChecker;

    public void run() {
        List<Transaction> transactions = transactionsToBeChecked();
        for (Transaction transaction : transactions) {
                //publish events if the transaction needs check
                doCheck(transaction, now);                }
        } 
    }

    private List<Transaction> transactionsToBeChecked() {
         return transactionRepository.findBy(transactionChecker
            .aToBeCheckedSpec());
    }
}

Annother application service listens the event and do the actual check:

public class CheckTransactionServiceImpl implements CheckTransactionService {
    private TransactionChecker transactionChecker;

    @Transactional
    public void check(final TransactionNo transactionNo) {
        Transaction transaction = transactionRepository.find(transactionNo);
        CheckResult result = transactionChecker.check(transaction);
        //handle check result
    }
}

The TransactionCheck is an online-payment-solution agnostic domain service:

public interface TransactionChecker {
/**
 * 
 * | data between online-payment provider and ours | txn STATUS | RESULT |<br>
 * | consistent | CLOSED | VALID |<br>
 * | inconsistent | CLOSED | INVALID |<br>
 * others omitted
 */
    CheckResult check(Transaction transaction);
/**
 * returns txn specification to filter to be checked ones.
 */
    ToBeCheckedSpecification aToBeCheckedSpec();
}

As you may see, both the application service and domain service are now stateless.

IMHO, Ping is a domain service(correlate to TxnChecker), but the monitor is kind of an application service(correlate to CheckingBatch).

Rictus answered 6/3, 2014 at 10:24 Comment(3)
This is a great answer and actually solves a few other problems I had in my application (Domain/Service layer mixes). Thank you very much for this!Reyreyes
@Marco Thanks, and I agree with you. But this application was built before I know CQRS :-(. I think it is still useful to an application without CQRS block support.Rictus
@Hippoom, well sagas are not directly related with CQRS they are more in the domain of event SOA. Sagas have a specific use, so defenitelly your code does the job, I just wanted to point out that Sagas should be the correct pattern to use with ddd/event soa.Cleisthenes
A
2

In DDD a long-running process is called Saga. It's typically implemented using Domain Events.

Here's some introduction to the topic: http://abdullin.com/journal/2010/9/26/theory-of-cqrs-command-handlers-sagas-ars-and-event-subscrip.html/

Antipyrine answered 7/3, 2014 at 13:31 Comment(0)
R
1

I once implemented a similar function, hope it helps :)

Our organization owns an online-payment handling application. Once a customer finishes payment, the online-payment provider sends use a notification indicating success or failure. Somtimes network failures occur, the notification may never reach our application. hence, here comes disgruntled customers. So an auto-checking mechanism is needed.

An application runner is responsible for keeping the check running:

public class CheckingBatch {
    private TransactionChecker transactionChecker;

    public void run() {
        List<Transaction> transactions = transactionsToBeChecked();
        for (Transaction transaction : transactions) {
                //publish events if the transaction needs check
                doCheck(transaction, now);                }
        } 
    }

    private List<Transaction> transactionsToBeChecked() {
         return transactionRepository.findBy(transactionChecker
            .aToBeCheckedSpec());
    }
}

Annother application service listens the event and do the actual check:

public class CheckTransactionServiceImpl implements CheckTransactionService {
    private TransactionChecker transactionChecker;

    @Transactional
    public void check(final TransactionNo transactionNo) {
        Transaction transaction = transactionRepository.find(transactionNo);
        CheckResult result = transactionChecker.check(transaction);
        //handle check result
    }
}

The TransactionCheck is an online-payment-solution agnostic domain service:

public interface TransactionChecker {
/**
 * 
 * | data between online-payment provider and ours | txn STATUS | RESULT |<br>
 * | consistent | CLOSED | VALID |<br>
 * | inconsistent | CLOSED | INVALID |<br>
 * others omitted
 */
    CheckResult check(Transaction transaction);
/**
 * returns txn specification to filter to be checked ones.
 */
    ToBeCheckedSpecification aToBeCheckedSpec();
}

As you may see, both the application service and domain service are now stateless.

IMHO, Ping is a domain service(correlate to TxnChecker), but the monitor is kind of an application service(correlate to CheckingBatch).

Rictus answered 6/3, 2014 at 10:24 Comment(3)
This is a great answer and actually solves a few other problems I had in my application (Domain/Service layer mixes). Thank you very much for this!Reyreyes
@Marco Thanks, and I agree with you. But this application was built before I know CQRS :-(. I think it is still useful to an application without CQRS block support.Rictus
@Hippoom, well sagas are not directly related with CQRS they are more in the domain of event SOA. Sagas have a specific use, so defenitelly your code does the job, I just wanted to point out that Sagas should be the correct pattern to use with ddd/event soa.Cleisthenes

© 2022 - 2024 — McMap. All rights reserved.