Mapping the same entity to different tables
Asked Answered
R

3

9

A bit of domain knowledge

I'm writing a POS (Point Of Sales) software which allows to pay goods or to refund them. When paying or refunding, one need to specify which money transfer mean to use: cash, EFT (~=credit card), loyalty card, voucher, etc.

These money transfer means are a finite and known set of values (a kind of enum).

The tricky part is that I need to be able to store a custom subset of these means for both payments and refunds (the two sets may be different) on the POS terminal.

For example:

  • Available payment means: Cash, EFT, Loyalty card, Voucher
  • Available refund means: Cash, Voucher

Current state of implementation

I choose to implement the concept of money transfer mean as follow:

public abstract class MoneyTransferMean : AggregateRoot
{
    public static readonly MoneyTransferMean Cash = new CashMoneyTransferMean();
    public static readonly MoneyTransferMean EFT = new EFTMoneyTransferMean();
    // and so on...

    //abstract method

    public class CashMoneyTransferMean : MoneyTransferMean
    {
        //impl of abstract method
    }

    public class EFTMoneyTransferMean : MoneyTransferMean
    {
        //impl of abstract method
    }

    //and so on...
}

The reason it is not a "plain enum" is that there exists some behavior that is inside these classes. I also had to declare inner classes public (instead of private) in order to reference them in FluentNHibernate mapping (see below).

How it is used

Both the payment and refund means are always stored or retrieved in/from the DB as a set. They are really two distinct sets even though some values inside both sets may be the same.

Use case 1: define a new set of payment/refund means

  • Delete all the existing payment/refund means
  • Insert the new ones

Use case 2: retrieve all the payment/refund means

  • Get a collection of all the stored payment/refund means

Problem

I'm stuck with my current design on the persistence aspect. I'm using NHibernate (with FluentNHibernate to declare class maps) and I can't find a way to map it to some valid DB schema.

I found that it is possible to map a class multiple times using entity-name however I'm not sure that it is possible with subclasses.

What I'm not ready to do is to alter the MoneyTransferMean public API to be able to persist it (for example adding a bool isRefund to differentiate between the two). However adding some private discriminator field or so is ok.

My current mapping:

public sealed class MoneyTransferMeanMap : ClassMap<MoneyTransferMean>
{
    public MoneyTransferMeanMap()
    {
        Id(Entity.Expressions<MoneyTransferMean>.Id);
        DiscriminateSubClassesOnColumn("Type")
            .Not.Nullable();
    }
}

public sealed class CashMoneyTransferMeanMap : SubclassMap<MoneyTransferMean.CashMoneyTransferMean>
{
    public CashMoneyTransferMeanMap()
    {
        DiscriminatorValue("Cash");
    }
}

public sealed class EFTMoneyTransferMeanMap : SubclassMap<MoneyTransferMean.EFTMoneyTransferMean>
{
    public EFTMoneyTransferMeanMap()
    {
        DiscriminatorValue("EFT");
    }
}

//and so on...

This mapping compiles however it only produces 1 table and I'm not able to differentiate between payment/refund when querying this table.

I tried to declare two mappings referencing both MoneyTransferMean with different table and entity-name however this leads me to an exception Duplicate class/entity mapping MoneyTransferMean+CashMoneyTransferMean.

I also tried to duplicate subclass mappings but I'm unable to specify a "parent mapping" which leads me to the same exception as above.

Question

Does a solution exist to persist my current domain entities ?

If not, what would be the smallest refactor I need to perform on my entities to make them persistable with NHibnernate ?

Rotherham answered 20/12, 2019 at 7:54 Comment(2)
Why do you want to store those payment methods in the database? What is the state there, apart from the name ?Flavone
@Flavone Indeed, storing these in the database looks a bit clunky. However this is a requirement from the project that all the state to be in the database.Rotherham
R
0

Finally, I decided to solve the problem by duplicating my entity MoneyTransferMean into two entities PaymentMean and RefundMean.

Although similar in implementation, the distinction between the two entities makes sense in the business and was for me the least worst solution.

Rotherham answered 13/1, 2020 at 7:45 Comment(0)
I
0

Why don't you create one single entity MoneyTransferMean, with all the common properties (fields) and just add 2 extra fields (boolean) to determine if that MoneyTransferMean is either Payment or Refund, or both???? Persist it or not.

Also it can be done with a extra Entity with Id (PK), add same extra fields, relationship would be 1:1 with MoneyTransferMean. Ugly, I know, but it should work.

Interlaken answered 5/1, 2020 at 12:9 Comment(1)
I don't want to add complexity that is not domain related into my domain project (such as adding boolean on which a subsequent if/else is needed). Also I don't want the people that will use these classes to make mistake (by forgetting to check the boolean and thinking that each value is a refund mean for example). It's all about explicitness.Rotherham
P
0

I'd second and add to what @DEVX75 suggested, in that your transaction types are essentially describing the same concept, though one is +ve whilst the other is -ve. I'd probably add just one boolean field though, and have separate records to discern refunds from payments.

Assuming you have a UID and are not using the means label name as the ID, you can allow duplicate names for means, and include two cash entries, for example:

UID, Label, IsRefund

1, Cash, false

2, Cash, true

3, Voucher, false

4, Voucher, true

Then you can easily get the following:

Transaction Type = MoneyTransferMean.IsRefund? "Refund" : "Payment"

Transaction Value = MoneyTransferMean.IsRefund? MoneyTransfer.amount * -1 : MoneyTransfer.amount

That way, if in your transactions you've referenced MoneyTransferMean.UID = 2, you know that that is a cash refund, rather than knowing that that is a transaction type that could be either a cash refund or a cash payment.

Poacher answered 5/1, 2020 at 17:54 Comment(1)
Ah bugger, just noticed you said you don't want to / can't edit the public API. Sorry / ignore my answer, though I'll leave it as perhaps it will be useful for others with similar problem / use-case.Poacher
R
0

Finally, I decided to solve the problem by duplicating my entity MoneyTransferMean into two entities PaymentMean and RefundMean.

Although similar in implementation, the distinction between the two entities makes sense in the business and was for me the least worst solution.

Rotherham answered 13/1, 2020 at 7:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.