I'm setting up new API to Setup LocalAccounts and need your advice on how to design the API with CQS
some background :
LocalAccount
Is Account that related to private customer or corporate customer and contain Debit or credit Account details with one or more cards (plastic and 1 virtual )
Example of LocalAccounts
: PrivateDebitLocalAccount
, CorpoeateCreditLocalAccount
, PrivateCreditLocalAccount
...
I want to use CQS to expose command for the Action on those entity : AddNewLocalAccountCommand
, UpadteLocalAccountCommand
and so on ..
My client doesn't want to work with lots of commands for the same action : 4 command to AddNew
for PrivatedebitLocalAccount
, CorpoeateCreditLocalAccount
,
CorpoeateDebitLocalAccount
, PrivateCreditLocalAccount
and then use 4 command to update and 4 Delete commands ...and so on
So I decided to Expose one Command for create all 4 types of localAccount AddNewLocalAccount
that will get flat object with all fields and in the WebAPI inside I map the request to my command (I Do this for Backward comparability on the Contract changes for My command ) and send it to Handler using MediatR library By Jimmy Bogard.
In The handler I Call AggrigateLocalAccountServices
with 2 dependencies of ServiceFactories
to get back the Domain object i need : AccountServiceFactory
and CustomerServiceFact
, this will help to get the relevant domain object from the command : Customer
(private or customer) and Account
based on AccountType
in the command (credit or debit)
Example of AddNewCorporateLocalAccountRequest
var LocalAccountData = new LocalAccountData(Guid.NewGuid())
{
CustomerType = CustomerTypes.Corporate,
AccountTypes = AccountTypes.Credit,
CreditLimit = 200,
Deposit = 100,
CompanyId = "43545",
MonthlyLimit = 50,
FirstName = "ddd",
LastName = "deee",
};
In My Controller WebApi I Maped the Request to command even it is mixed fields from Debit and credit ..
var command = new AddNewLocalAccountCommand(LocalAccountData.Id ==
Guid.Empty ? Guid.NewGuid() : LocalAccountData.Id)
{
CompanyId = LocalAccountData.CompanyId, //Corporate field
CustomerType = LocalAccountData.CustomerType, //define the
CustomerType (private/Corporate) for LocalAccountFactory (customer)
AccountTypes = LocalAccountData.AccountTypes,//define the
AccountType(debit/credit) for AccountFactory (customer)
CreditLimit = LocalAccountData.CreditLimit, //Credit data
};
//send to commandHandler using Mediatr :
var x = new AddNewLocalAccountCommandHandler(
new LocalAccountAggregator(
localAccountServiceFactory, accountFactoryService));
var result = mediatR.Send(command);
In my commandHandler method
public void Handle(AddNewLocalAccountCommand command){
// map again all data to local account DTO so i can ask the factories in AggRoot to extract the domain i need
var localAccountData = new LocalAccountData(command.Id)
{
CustomerType = command.CustomerType,
AccountTypes = command.AccountTypes,
Balance = command.Balance,
....
};
var customerAccontId =
this._localAccountAggregator
.AddNewLocalAccount(localAccountData);
};
My Agg root class I expose interface for adding the localAccount and this is how is the implementation
public virtual Guid AddNewLocalAccount(ILocalAccountData data)
{
var localAccount = CreateLocalAccount(data);
var vc = CreateVirtualCard();
localAccount.Accounts.Cards.Add(vc);
// now I need to decide where to map from localAccount to my EF
// object, here or should it be inside the Service that know the
// right type of tables to be mapped
customerDto = Mapper.Map<localAccount, Customer>(request);
_context.Customers.Add(customerDto);
_context.SaveChanges();
return Guid.NewGuid();
}
private ILocalAccount CreateLocalAccount (ILocalAccountData data)
{
var accountService =
_accountFactoryService.GetService(data.AccountTypes);
var localAccountFactoryService =
_localAccountFactoryService.GetService(data);
//create method will be call from the right LocalAccountService
that get the right domain object.
return localAccountFactoryService.Create(data);
}
Here is one of the service that can create the right localAccount Type based on the data from client :
public class CorporateLocalAccountService : ILocalAccountService
{
private IAccountService _account;
public CorporateLocalAccountService(IAccountService account)
{
_account = account;
}
public ILocalAccount CreateData(ILocalAccountData data)
{
return new CorporateLocalAccount(new Entity(data.Id))
{
Account = _account.Create(data),
Contact = new Person { LastName = data.LastName, FirstName = data.FirstName, MobilePhoneNumber = data.MobilePhone },
CompanyId = data.CompanyId,
CompanyName = data.CompanyName,
};
}
}
}
I have some question about this design :
My Client Doesn't know my internal Domain so they can do mistake and send fields of debit account in Credit Account request as i Use one container object , Beside sending JSON example of possible valid contract Or Change from Flat fields to object Inside my
LocalAccountData
:DebitAccountData
,CreditAccountData
,PrivateData
,CorporateData
. How can i tell my client not do do mistake in the request? Should I change my API request ?In my
CorporateLocalAccountService
I return the domain object to the aggregate , should this class create the data in DB using EF or just return domain model and Aggregate will create it ?I'm Working with the Command Query and in
CommandHander
I call theAggregate
Object that need to create localAccount (insert data to 4-5 tables and create virtual Card as a result of creating theLocalAccount
and send it back to client ) How Do I work with my EntityFramework ? I mean do I need repositories or should i call theDBContext
and use the EF objects inside the Aggregate directly and avoid Repositories ? // I warp the commandHandler withTransactionCommandHandler
so Do I need the transaction scope in the Aggregate class or i can use the transaction scope from my commandHandler decorator ?
Thanks