Should aggregates only contain methods which make sense in terms of business rules?

106 views
Skip to first unread message

si...@programujem.eu

unread,
May 4, 2018, 1:18:37 PM5/4/18
to DDD/CQRS
I work for a FinTech company on an income/expense tracker. Users in our system can create wallets and manually add their incomes/expenses to them. Users may have access to multiple wallets and a single wallet may be shared among multiple users. We're currently working on a feature to allow users to transfer funds from one wallet to another and I have come across some modelling issues. There are two main concepts, one way transfer and a two way transfer (two way transfer is basically composed of two one way transfers which are linked together).

Here are some business rules which were/are valid up until now (the introduction of the transfers feature):

- user can only add a transaction to a wallet to which the user has access,
- a transaction can only be edited in an active wallet (i.e. it must not be locked or deleted),
- a transaction can only be edited by a user having access to the wallet in which the transaction resides,
- the editable transaction's attributes are: note, amount, date when the transaction has been made, reminder and repetition (we're not a bank, so the transactions are actually mutable).

Up until now, I didn't really have a problem of modelling this behaviour, and a transaction looked something like this:

class UserId { }

class User { public final UserId id; }

enum State { ACTIVE, LOCKED, DELETED, }

class WalletId { }

class Wallet {
    private WalletId id;
    private State currentState;
    private Set<UserId> usersWithAccess;

    public boolean isActive() { return currentState.equals(State.ACTIVE); }

    public boolean userHasAccessToWallet(UserId id) { return usersWithAccess.contains(id); }
}

class TransactionId { }

class Transaction {
    private TransactionId id;
    private WalletId walletId;
    private BigDecimal amount;
    private String note;
    private UserId madeById;
    private Date madeOn;

    public void changeAmount(User initiatedBy, Wallet wallet, BigDecimal newAmount) {
        if (!wallet.isActive()) {
            throw new IllegalStateException("Cannot change amount of a transaction in an inactive wallet.");
        }

        if (!wallet.userHasAccessToWallet(initiatedBy.id)) {
            throw new IllegalStateException("You do not have access to a wallet in which the transaction resides.");
        }

        amount = newAmount;
    }

    ... other methods, e.g. to update repetition, reminder,...
}

With the feature introduction, several new rules have been however introduced. Even though in the display list a transfer is still visible among regular transactions, there have been behavioural changes:

- it is not longer possible to change any attribute of a transfer transaction besides its note,
- transfer transaction can only be updated by the user it has been made on (unlike a regular transaction which can be changed by anyone having access to it).

Those are just two changed rules from a few others. Initially, I thought I would simply add a Type attribute to a transaction, distinguishing whether it's a transfer or a regular transaction, however what I didn't like is the idea of having a condition check for the type in almost all change methods on the transaction, effectively forbidding the operation because it's not allowed by a business rule.

For the simple idea of a one way transfer, my idea was to create a new aggregate OneWayTransfer which would only contain a method to update a note and not even allow anything else. The problem is, a transfer still really is a transaction only with a different set of rules. On top of that, a regular transaction may be upgraded to a transfer and once that's done, the transfer is once again limited to only updates of certain properties.

Could you suggest some sensible approach to modelling this domain problem? I am open to suggestions.

Daniel Hardt

unread,
May 4, 2018, 5:33:38 PM5/4/18
to DDD/CQRS
Why is it possible to edit a transaction after this transaction is done?
So basically you can change the amout of the transfered fund after this funds are on the other wallet?
I don't get it. Sorry.

instead of change anything make a new transaction and for the sake of transparancy, you didn't change any note after something like a transaction is done. at least have a history of notes or something.

so have too less information of the domain to give you any advise.

so in my head. A transaction is immutable (like events in a eventstore). If you have to change something you make a new transation. Maybe you transfer 100 of something. than oh it was too much, than transfer 50 back. an in the sum, you get a wanted state.
i think your transaction are events. some things, that have happend or am i wrong?

Rickard Öberg

unread,
May 5, 2018, 12:17:25 AM5/5/18
to ddd...@googlegroups.com
Hi Simon,

On Sat, May 5, 2018 at 1:18 AM <si...@programujem.eu> wrote:
> Here are some business rules which were/are valid up until now (the
introduction of the transfers feature):

> - user can only add a transaction to a wallet to which the user has
access,
> - a transaction can only be edited in an active wallet (i.e. it must not
be locked or deleted),
> - a transaction can only be edited by a user having access to the wallet
in which the transaction resides,
> - the editable transaction's attributes are: note, amount, date when the
transaction has been made, reminder and repetition (we're not a bank, so
the transactions are actually mutable).

From your description of domain, doesn't it make more sense to separate
between a transaction template (editable, has repetition and reminder) and
an actual transaction (a single transaction that happened at a particular
date). Why keep these two in one? Seems like a fair bit of complexity comes
from this.

/Rickard

si...@programujem.eu

unread,
May 5, 2018, 1:14:53 AM5/5/18
to DDD/CQRS
We're not an institution with a bank-like strict policies, so mutability needs to be preserved. Our product is basically a prettified excel and with the introduction of transfers we want to introduce some way how to keep consistency between two linked transactions indicating a transfer.

Dne pátek 4. května 2018 23:33:38 UTC+2 Daniel Hardt napsal(a):

si...@programujem.eu

unread,
May 5, 2018, 1:16:37 AM5/5/18
to DDD/CQRS
I wish the initial developer had actually discussed and done something like this. Unfortunately, as I have already replied to Daniel, transaction mutability has existed in the system since its inception and is to be preserved at this moment.

Dne sobota 5. května 2018 6:17:25 UTC+2 Rickard Öberg napsal(a):

Rickard Öberg

unread,
May 5, 2018, 1:33:03 AM5/5/18
to ddd...@googlegroups.com
On Sat, May 5, 2018 at 1:16 PM <si...@programujem.eu> wrote:

> I wish the initial developer had actually discussed and done something
like this. Unfortunately, as I have already replied to Daniel, transaction
mutability has existed in the system since its inception and is to be
preserved at this moment.

So here it depends quite a bit on how you've actually set things up. In my
case, because I have EventSourcing and ability to do full blue/green
deployments, I can do these kinds of domain refactorings without downtime.
I'm in the middle of one just like this at the moment as it happens (split
one concept into two).

This may not be applicable to you, but maybe the description could give you
some inspiration, idk.

In my case I have Subscription->Product which needs to be split up into
Subscription->Plan->Product. I've refactored/redesigned my codebase to have
this new layout of aggregates, entities, and events. I then fire up a
cluster with the new design, and import events from existing production. In
the import there's a migration step that transforms events in the old
design to events in the new design, so the intent is kept but the specific
layout is new. I create new aggregates if necessary, and migrate/apply old
events as if they had been applied in the new design. Once the new cluster
is in realtime sync with the old system we can turn the load balancers to
the new system, and turn the old one off. With this scheme we can (and
have/will) do pretty much arbitrarily complex redesigns as long as there is
a mapping from old to new, somehow.

The above depends quite a bit on what infrastructure you have available to
you, and is relying on the fact that it is using EventSourcing. If you
don't, yeah, then it's way way more complicated.

regards, Rickard
> --
> You received this message because you are subscribed to the Google Groups
"DDD/CQRS" group.
> To unsubscribe from this group and stop receiving emails from it, send an
email to dddcqrs+u...@googlegroups.com.
> Visit this group at https://groups.google.com/group/dddcqrs.
> For more options, visit https://groups.google.com/d/optout.

Daniel Hardt

unread,
May 5, 2018, 4:15:11 AM5/5/18
to DDD/CQRS
That a good approache in my opinion.

@si...

So maybe you are able in the first step to event source your mutable transactions. So every mutation is an event.
And if you event sourcing your transaction in production, you are able to refactor your domain model of the transactions like Rickard said.

@Rickard
The splitting into template and actual transaction sounds reasonable. 
But maybe we know to little about the whole domain. (@si.... this is not request to tell us all about your business secrets ;) )
Reply all
Reply to author
Forward
0 new messages