Domain Model

BasicCRUD Insert Query with Bridge

Now, let us look at the Domain model. What is domain model?

The domain model is a representation of your data structure as it appears in your database and it also contains the logic for manipulating the data inside the domain model, So first we will look at the insert customer.

Let's look at the DomainModels folder, if you have seen two files created one is the customer. It's a partial class and another is also a partial class customer with an AddCustomer method.

So here in the customer domain model, I have added two properties name and date of birth for each property. Ideally should be a protected setter so that inherited classes.

public partial class Customer : DomainModelBridge
{
readonly ILogger<Customer> _logger;
protected Customer()
{
}
public Customer(ILogger<Customer> logger)
{
_logger = logger;
}
#region "Attributes"
#region "Protected"
[StringLength(50)]
public string Name { get; protected set; }
public DateTime DateOfBirth { get; protected set; }
#endregion
#endregion
}

We are not keeping it private and avoid using the public accessor in the domain model so that all the values are manipulated only through the method that you have provided and it can be used by the Industry and Client layers later.

So here I am taking an AddCustomer. If you remember we had the command which had the InputAPIModel and the ID coming in from the Services, and that contains a command which contains your FlexHostContextInfo within the command.

public virtual Customer AddCustomer(AddCustomerCommand cmd)
{
Guard.AgainstNull("Customer command cannot be empty", cmd);
this.Convert(cmd.model);
this.CreatedBy = cmd.HostContextInfo.UserId;
this.LastModifiedBy = cmd.HostContextInfo.UserId;
//Map any other field not handled by Automapper config
this.SetAdded(cmd.Id);
//Set your appropriate SetAdded for the inner object here
return this;
}

So here I am mapping the InputAPIModel into the customer object and then mapping the CreatedBy ModifiedBy is not available in the input APIModel since it has to be derived from the IHttpContextAccessor.

It is put in the context info and we are mapping the user ID for the model and you can map any other field here manually. Mapping inside that so how this convert happens is we have a mapper here. For each module, you will get a new mapper file.

So here I am mapping the InputAPIModel, whatever is in the add customer input tape a model gets mapped to the customer. it Maps according to your mappings as provided in your Mappers project.

You can refer to the AutoMapper documentation for more information how to map from one object to another and also you can configure your automapper here for any other special mappings.

By default we have to create a mapper here. Whenever you create any input. First thing that you do is to create your properties, whatever you need for the model and then second with your mapping here so that the mapping can happen.

public class CustomerMapperConfiguration : FlexMapperProfile
{
/// <summary>
///
/// </summary>
public CustomerMapperConfiguration() : base()
{
#region Input
//Sample:
//CreateMap<YourAPIModel, YourDomainModel>();
CreateMap<AddCustomerInputAPIModel, Customer>();
CreateMap<UpdateCustomerInputAPIModel, Customer>();
#endregion
#region Output
//Sample:
//CreateMap<YourDomainModel, YourOutputAPIModel>();
CreateMap<Customer, GetCustomerByIdOutputAPIModel>();
CreateMap<Customer, GetCustomersForLookupOutputAPIModel>();
CreateMap<Customer, GetCustomersOutputAPIModel>();
#endregion
}
}

If mapping is missing, the domain method and the query ProjectTo will give error while mapping data.

Next, we will look at customizing the domain model. Ideally you should give here all the other validation related to the model in your DbContext.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//Use this for adding indexes or other customizations to tables pertaining to EF implementation
modelBuilder.Entity<Customer>().HasIndex(nameUnique => nameUnique.Name).IsUnique();
}

The example here is using the fluent API of Entity framework.

One benefit of using it this way is, If you want to switch Technologies in future for the reason avoid any EF related stuff inside the DomainModel itself to keep it decoupled. Otherwise, you will be tied to EF syntax here inside the domain model and the domain model project does not refer any EFCore stuff and keep things decoupled. So all the other validations which are not generic in nature can go to the DBContext. Now our model is created. We have the fields we have the mapping ready for us now, let's see what next and one more thing.

This domain model method is called in the Handler plugin. So whenever you insert a customer. This method is being called in the Handler.

public partial class AddCustomerPlugin : FlexiPluginBase, IFlexiPlugin<AddCustomerPostBusDataPacket>
{
public override string Id { get; set; } = "39f682d485cdecf2c3b66da16f866f05";
public override string FriendlyName { get; set; } = "AddCustomerPlugin";
protected string OnRaiseEventCondition = "";
IFlexRepositoryBridge _repo;
readonly ILogger<AddCustomerPlugin> _logger;
Customer _model;
IFlexHost _flexHost;
IWriteDbConnectionProviderBridge _connectionProvider;
/// <summary>
///
/// </summary>
/// <param name="repo"></param>
/// <param name="logger"></param>
public AddCustomerPlugin(IFlexRepositoryBridge repo, ILogger<AddCustomerPlugin> logger, IFlexHost flexHost, IWriteDbConnectionProviderBridge connectionProvider)
{
_repo = repo;
_logger = logger;
_flexHost = flexHost;
_connectionProvider = connectionProvider;
}
/// <summary>
///
/// </summary>
/// <param name="packet"></param>
public async Task Execute(AddCustomerPostBusDataPacket packet)
{
_connectionProvider.ConfigureDbConnectionString(packet.cmd.HostContextInfo);
_repo.InitializeConnection(_connectionProvider);
_model = _flexHost.GetDomainModel<Customer>().AddCustomer(packet.cmd);
_repo.InsertOrUpdate(_model);
await _repo.SaveAsync();
_logger.LogDebug("Customer inserted into Database: " + _model.Id);
//TODO: Specify your condition to raise event here...
//TODO: Set the value of OnRaiseEventCondition according to your business logic
OnRaiseEventCondition = CONDITION_ONSUCCESS;
RaiseEventCondition raiseEventCondition = new RaiseEventCondition(OnRaiseEventCondition);
await raiseEventCondition.RaiseEvent<AddCustomerPlugin>(this, new object[] { packet.FlexServiceBusContext });
}
}

So if you see above, the Handler doesn't carry any logic.

All the logic related to populating the customer while adding the customer is inside the DomainModel and the Handler just call the method and populate create the model and save it in the database

Now let me explain the Customer DomainModel is inherited from DomainModelBridge. The bridge is inherited from DomainModel, which is the flex domain model. This lets you customize existing fields as well as add any common fields to be used across all domain models. The Id is by default 32 characters long. If you want to customize the Id or increase the length, you can do it like this;

public abstract class DomainModelBridge : DomainModel
{
[StringLength(50)]
public override string Id { get; protected set; }
[StringLength(100)]
public string YourCommonCustomField { get; protected set; }
}

We want to change the length of the ID or we have to add any common fields, which you want to use across all the domain models. You can always go and do it there. If you want to change the length of the ID you can always do it there.

We don't need anything now because our Sequential GUID that is just being generated is 32 characters and that is more than enough.

Now let us get into the migration.