2

So I've done a good job separating the DAL from the BLL. I use repositories for each aggregate root. The issue I'm having is database concurrency with IDs in the domain.

I don't want to use the surrogate key in the domain layer (e.g. GUID). So the aggregate root I have now is a Journal and that Journal has a list of transactions. The first table is Journals and the second table is Transactions that map back to the first table.

It looks like this:

Journals

+----------------------------------------+
|  ID  | JournalID | LastTransactionID   |
+----------------------------------------+
|{a-as}| 1         | 8                   |
|{ase1}| 2         | 1                   |
|......|...........|.....................|
|{1sas}| 45        | 4                   |
+----------------------------------------+

Transactions

+----------------------------------------------------+
| ID   | JournalID | TransactionID | Account | Amount|
+----------------------------------------------------+
|{a2ba}| 1         | 1             | xxxxxxx | 10.20 |
|{2a4a}| 1         | 2             | xxxxxxx | 1.10  |
|{20s9}| 2         | 1             | xxxxxxx | 93.12 |
|......|...........|...............|.........|.......|
|{9eka}| 45        | 4             | xxxxxxx | 291.10|
+----------------------------------------------------+

So I use the LastTransactionID from the Journals column to figure out how to assign a domain ID (TransactionID) to new transactions. The issue I'm having is if more than one desktop app instance is accessing the same Journal then the LastTransactionID could be stale and causes issues with trying to insert new transactions.

My domain classes look like:

class Journal
{
    public int JournalID { get; private set; }
    public int LastTransactionID { get; private set; }
    public IEnumerable<Transaction> Transactions { get; private set; }

    public Journal(int journalID, int lastTransactionID, IEnumerable<Transaction> transactions)
    {
        // do work......
    }
 }

class Transaction
{
    public int JournalID { get; private set; }
    public int TransactionID { get; private set; }
    public Account Account { get; private set; }
    public Money Amount { get; private set; }

    public Journal(int journalID, int transactionID, Account account, Money amount)
    {
        // do work......
    }
 }

I'm using EF and have this encapsulated in my repository. Maybe I'm using a poor database design?

  • Do you need to let Journal control the id of Transaction? Do you need to be able to generate the id of Transaction in the client, or can you let the db do it for you - for example with an Identity strategy? Why don't you persist your Transactions to the db and then update Journal with the last transaction id - all within the same atomic operation? – Øyvind Apr 18 '18 at 7:27
  • Since Journal is the Aggregate Root (and therefore Transaction falls under that), it seems like the most sense. I may be wrong. I don’t want to use the DB generated PKs for a couple reasons. A GUID doesn’t make sense here. If I use an auto-incrementing int, this could work but then I bring persistence details into the domain. For example, Journal A could have transactions 1 and 43 where B has transactions 2 and 24. I think it would make most sense if transaction IDs were duplicated between journals so A could have 1 and 3 and B could have 2 and 3. Obviously if the client deleted – keelerjr12 Apr 18 '18 at 10:39
  • Transactions, that ID would no longer appear. This would be similar to a static int that is just used as a counter. I’m using DB/EF transactions in my service layer so the operation is largely atomic. But I guess the issue is when the Journal is pulled from the database early in the application lifecycle. This allows for multiple instances to reference the same Journal and therefore the counter (ie LastTransactionID) could be stale. – keelerjr12 Apr 18 '18 at 10:42
  • For example Journal A is pulled by App A, and Journal A is pulled by App B. App A adds a new transaction A that gets an Id of 5 (from Journal’s LastTransactionID), but Transaction B gets the same ID of 5. Now each app tries to persist an update to the DB for Journal. Which Transaction gets saved? They both might with the same Transaction ID, but different PK/GUID... – keelerjr12 Apr 18 '18 at 10:45
  • Additionally, since Transaction is an entity local to Journal (ie part of the Journal Aggregate), I think it’s important for its ID to be local to that’s aggregate (eg Transaction ID vs a table PK). However, in the domain a transaction would be referenced via the Journal (the AR). – keelerjr12 Apr 18 '18 at 10:54
1

I don't want to use the surrogate key in the domain layer

I have no idea why you wouldn't want to use this. Your BLL is just reinventing the wheel by trying to manage and create it's own unique ID. Let the database do the tasks it does best.

By having a database that can create and enforce a unique value and make it available to other users of the system, that's how you manage concurrency.

Otherwise, you need to store the ID created in the BLL somewhere other parts/instances of the application can access it. Even if you're holding it in memory instead of on a disk, it's just another part of your data storage.

The only advantage to what you're doing is if you switched to a database that did not have the ability to create its own unique ID's.

  • One advantage is I could use the local TransactionID to determine the order of when transactions occurred without adding a TimeCreated column. So if they have the same dates I can use this to order them by this criteria. – keelerjr12 Apr 18 '18 at 14:47
  • Additionally, there was a thread on this on here that talked about the German Tank problem that says by exposing the underlying surrogate key you’re exposing persistence details that then can be used to figure out the details of your application/infrastructure. E.g. using a global incrementing primary key, you can determine how many records there are, the rate at which rows are added, etc. – keelerjr12 Apr 18 '18 at 14:49
  • softwareengineering.stackexchange.com/questions/328458/… please see comments here. – keelerjr12 Apr 18 '18 at 14:52
  • I think the more important concept here is that in the accounting domain (since I’m using DDD), there is no such thing as a GUID, but there are Transaction IDs/numbers in a Journal. So injecting technical details into the business domain does not make sense (e.g it’s not part of the ubiquitous language). – keelerjr12 Apr 18 '18 at 15:22
  • If you bring another dev to your project, I don't think they would be too surprised that those technical ID's were in your domain. Security is an issue no matter what. I'd rather someone know my GUID from some system than any personal/real world information. – JeffO Apr 18 '18 at 19:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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