Search Results for

    Show / Hide Table of Contents

    Tutorial: Advanced Mapping

    Overview

    This tutorial will guide you through setting up an advanced mapping configuration and corresponding consuming code in a template between a CQRS Command and a Domain Entity Class using Intent Architect. This allows for automatic code generation that maps properties from a command (or DTO) to associated properties or fields of a domain entity.

    Advanced mapping

    Create a new Module Builder Application

    • On the Intent Architect home screen select the Create a new module button on the left under Get started.

    • On the create application dialogue set the Name to ElementMappingModule and press NEXT.

    • On the component selection screen of the dialogue, ensure the following optional components are selected:

      • Module Builder - C#
      • Domain
      • Services
    • Press the CREATE button and wait for the application creation process to complete.

    • Right-click on the ElementMappingModule application in the Solution Explorer and select the Manage Modules option.

    • Search for cqrs.

    • Select the Intent.Modelers.Services.CQRS search result.

    • Expand the Options dropdown in the right pane and check the Install metadata only option:

      The install metadata only option

    • Press the Install button on the right.

    Create the Designer Settings

    Designer settings enable the definition of new designer components such as elements, associations and mapping configurations as well as extending the behaviour of existing designer components.

    Highly customizable and configurable designers is a key feature of Intent Architect as it allows the creation of visual designers which are perfectly suited to developers using Intent Architect to design their applications for particular technologies or architectural concepts.

    Designer settings

    • Open the Module Builder designer from the Solution Explorer.

    • Right-click the package (the root node of the tree view) and select the Add Designers Folder.

    • Right-click the designers folder and select the New Designer Setting option and give it the name Explicit Mapper Settings.

    • In Properties pane for the Extend Designers property add Services:

      The "Extend Designers" "Designer Settings" property

    Define Associations onto which Advanced Mapping configurations will be added

    Defining mapping associations allows users to establish explicit relationships between elements, e.g. between Commands and domain Classes, we are then able to add any number of Advanced Mapping configurations onto these associations which users can leverage as needed.

    Designer Associations

    • Right-click on Explicit Mapper Settings, select the New Association Type option and give it a name of Map to Element.
    • Select the Map to Element Source End element in the tree view and under Settings in the Properties pane on the right:
      • For the Target Type property set it to Command:

        Target Types property

      • For the Display Text Function property, use the pencil icon on the right of the text field to bring up a JavaScript code editor. We're going to want to return a string which makes key information about the mapping visible in the designer. You can enter the following into the code editor dialogue:

        return `mapped by : ${typeReference.getType().getParent().getName()}.${typeReference.display}`;
        
      • Set the Name Accessibility property to Hidden, this will prevent users from being able to change the name of the association.

    • Select the Map to Element Target End element and under Settings in the Properties pane on the right:
      • Set the Target Type property to Class.

      • Set the Display Text Function property to:

        const returnType = typeReference.getType()?.typeReference?.display ?? "void";
        return `[map] ${getName()}: ${`${typeReference.getType()?.getName()}(...): ${returnType}` ?? "<not set>"}`;
        
      • Set the Name Accessibility property to Optional.

    • Right-click on the Map to Element element, select the Add Visual Settings option and ensure the Type is set to void.
    • Select the [visual] element in the tree view and in the Properties pane on the right:
      • Set the Line Type property to Curved.
      • Set the Line Dash Array property to return "3, 7";.
    • Right-click on the [visual] element and select the Add Source Visual option.
    • Select the [source] element in the tree view and in the Properties pane on the right:
      • Under the Point Settings section:
        • Set the Path property to the following to control the SVG path to define the visual for the end of the line, in this case an arrow:

          return `a 4,4 0 1,0 4,4 a 4,4 0 1,0 -4,4`;
          
        • Set the Line Width property to return 3;.

        • We have now configured that a solid circle is to be drawn on the Source end.

    • Right-click on the [visual] element and select the Add Destination Visual option.
    • Select the [source] element in the tree view and in the Properties pane on the right:
      • Under the Point Settings section:
        • Set the Path property to the following to have it in this case draw a triangle:

          return `l 4 8 l -8 0 l 4 -8 Z`;
          

    Define the Element Mapping

    Element mappings specify how different element types (like DTOs and Commands) should map to the target (like Domain Classes and Associations). This allows for detailed configuration of how data should be transferred and transformed between different parts of the application.

    Element Mapping

    The Source elements within the settings (such as Command or DTO with nested Fields) indicate the elements from which you can map. On the other hand, the Target elements (such as Class, Attribute, or Associations) indicate where you can map to. This setup ensures there's a clear and traversable path for data or actions between various parts of your model.

    Right-click on the Explicit Mapper Settings element on the tree view, select the New Mapping Settings option and give it a name of Element Mapping.

    Starting on the Source element, right-click it and select the Add Mappable Element option to create tree structure:

    • Name of Command with it's Target Type set to Command.
    • Name of DTO with it's Target Type set to DTO
      • Name of Field with it's Target Type set to DTO-Field
      • Name of Collection Field with it's Target Type set to DTO-Field.

    The next steps will be addressing the following property types:

    • Represents: . This will be further addressed in the Invocation Mapping and Data Mapping.
    • Is Mappable Function: If it returns true the element is allowed to be mapped from/to. This is useful in cases where elements may be greyed out since they are read-only for instance on the target end.
    • Allow Multiple Mappings: Allow for the element to be mapped from/to multiple times.
    • Can Be Modified: Should it be permissible to generate and automatically link these fields if they do not already exist at the other end upon a double-click action (as an example)?
    • Traversable Mode: This mode enables elements that link to other types of elements to be expanded, making their child elements accessible for mapping. By choosing this mode, you can specify which types of elements are permitted to enter the traversal. For instance, consider a Class Order that contains a collection of OrderLines as an association; using this mode, you can navigate through the collection to map to its constituent elements.
    • Use Child Mappings From: Rather than replicating mappings for child elements along with their associated property values, you have the option to "re-use" the child-element structure from another element type. For instance, Command could adopt the child mappings from DTO, given their structural similarity despite serving different purposes.

    Set the properties for the following source elements:

    • Command

      • Represents Data.
      • Is Mappable Function return true;.
      • Allow Multiple Mappings checked.
      • Can Be Modified checked.
      • Use Child Mappings From DTO: DTO.
    • DTO

      • Represents Data.

      • Is Mappable Function return false;.

      • Allow Multiple Mappings checked.

      • Can Be Modified checked.

      • Create Name Function:

        return element.getParent('Command').getName() + element.getName() + 'Dto';
        
    • Field

      • Represents Data.

      • Filter Function:

        return !element.typeReference.getIsCollection();
        
      • Is Mappable Function return true;.

      • Allow Multiple Mappings checked.

      • Traversable Mode Traverse Specific Types.

      • Traversable Types DTO: DTO.

      • Can Be Modified checked.

    • Collection Field

      • Represents Data.

      • Filter Function:

        return element.typeReference.getIsCollection();
        
      • Is Mappable Function return true;.

      • Allow Multiple Mappings checked.

      • Traversable Types DTO: DTO.

      • Can Be Modified checked.

    Create the following Mappable Element Settings inside the Target (or Destination) element (with its own Target Type) by right-clicking on Target element and selecting the Add Mappable Element option:

    • Name of Create Class with it's Target Type set to Class.
      • Name of Set Attribute with it's Target Type set to Attribute.
      • Name of Set Association Target End with it's Target Type set to Association Target End.
      • Name of Set Association Source End with it's Target Type set to Association Source End.

    Set the properties for the following target elements:

    • Create Class
      • Represents Invokable.
      • Is Mappable Function return true;.
      • Allow Multiple Mappings checked.
    • Set Attribute
      • Represents Data.
      • Is Mappable Function return true;.
      • Allow Multiple Mappings checked.
    • Set Association Target End
      • Represents Data.
      • Is Mappable Function return true;.
      • Allow Multiple Mappings checked.
      • Traversable Mode Traverse Specific Types.
      • Traversable Types Create Class: Class.
      • Use Child Mappings From Create Class: Class.
    • Set Association Source End
      • Represents Data.
      • Is Required Function return false;.
      • Is Mappable Function return true;.
      • Allow Multiple Mappings checked.
      • Traversable Mode Traverse Specific Types.
      • Traversable Types Create Class: Class.
      • Use Child Mappings From Create Class: Class.

    Add the following Mapping Types to the Element Mapping by right-clicking and selecting the Add Mapping Type option and set their properties accordingly:

    Invocation mapping

    • Source Types Command: Command.
    • Target Types Create Class: Class.
    • Represents Invokable.

    Invocation mapping

    Example of an Invocation mapping.

    This mapping method facilitates the creation of an instance or execution of an operation on a target Entity. This can be done by directly calling the Entity's constructor, either explicitly or implicitly, or by solely executing the Entity's operation. To illustrate, a CreateOrderCommand could correspond to the constructor of an Order class, initializing necessary fields directly.

    Data mapping

    • Source Types Field: DTO-Field, Collection Field: DTO-Field.
    • Target Types Set Attribute: Attribute, Set Association Target End: Association Target End.
    • Represents Data.

    Data Mapping

    Example of a Data mapping.

    This type of mapping is intended for direct assignment of values from fields in the source object to attributes or relationships in the target object. It handles both s ingle-value fields and collections. By defining these mappings, fields from a command or DTO can be accurately transferred and mapped to corresponding properties of a domain entity, ensuring data integrity and consistency. For example, the RefNo and CreatedDate attributes of an Order entity can be directly mapped from fields in a command or DTO, ensuring each attribute is correctly populated.

    Create Command element extensions

    An Element Extension allows extension of behaviour of an Element defined in another module, such as adding a context menu option. We're going to want to want to extend Command elements to have a context menu option to:

    • Right-click the Explicit Mapper Settings element and select the Within this folder, create a Designer Settings item named Command Extension.
    • Set the Extended Designers property to reference the Services designer.
    • Add an Element Extension with the name Command Extension and target the Command type from the CQRS module.

    Add a context menu option to Commands to create a mapping

    To provide a way for users to create these Element Mappings between Commands and Domain Classes and to map the data flow between them, context menus will be added to enable the user to perform those functions.

    To create the Map To Element association and perform the Element Mapping mapping, we need to set up their context menu options.

    On the Command Extension, ensure that the [context menu] element exists by right-clicking on it and selecting the Add Menu Options option.

    On the [context menu] element, right-click and selecting the Add Association Creation option. Name it Add element mapping and set the type to Map To Element.

    Next, go to the Map To Element association and locate the Map To Element Target End destination end. Ensure it too has the [context menu] created by selecting Add Menu Options when right-clicking on the element.

    On the [context menu], right-click and select Add Mapping Option and name it Map to Element with the type being Element Mapping.

    Context menus

    Interpreting a mapping from a template to generate code

    Creating a template for mapping code involves defining a custom template that generates the necessary code to perform the mappings. This step ensures that the mappings defined in the designer are translated into executable code.

    Template for Mapping code

    Create a new template in the ElementMappingModule for mapping commands to domain entities by right-clicking on the ElementMappingModule package and selecting the New C# Template option.

    Ensure the following properties are set:

    • Name ElementMappingTemplate.
    • Type Single File.
    • Templating Method C# File Builder.
    • Designer Services.
    • Model Type Command.

    Implement ElementMappingTemplate

    The implementation of the ElementMappingTemplate and ElementMappingTypeResolver provides the logic for how the mappings should be executed and ensures that the correct methods and logic are generated based on the defined mappings.

    Run the Software Factory and open the solution in Visual Studio.

    Open the ElementMappingTemplatePartial class. Implement the constructor like this:

    public ElementMappingTemplate(IOutputTarget outputTarget, IList<CommandModel> model) : base(TemplateId, outputTarget, model)
    {
        CSharpFile = new CSharpFile(this.GetNamespace(), this.GetFolderPath())
            .AddClass($"ElementMapping", @class =>
            {
                @class.Static();
                foreach (var commandModel in model)
                {
                    var commandTypeName = GetTypeName("Application.Command", commandModel);
                    foreach (var target in commandModel.MapToElementTargets())
                    {
                        var entityTypeName = GetTypeName("Domain.Entity", target.Association.TargetEnd.Element);
                        var entityName = target.Association.TargetEnd.Element.Name;
                        @class.AddMethod(entityTypeName, $"MapTo{entityName.ToPascalCase()}", method =>
                        {
                            method.Static();
                            method.AddParameter(commandTypeName, "source", param => param.WithThisModifier());
    
                            var manager = new CSharpClassMappingManager(this);
    
                            manager.SetFromReplacement(commandModel, "source");
    
                            var resultStatement = new CSharpAssignmentStatement(
                                "var result",
                                manager.GenerateCreationStatement(target.Mappings.First())).WithSemicolon();
                            method.AddStatement(resultStatement);
                            method.AddStatement("return result;");
                        });
                    }
                }
            });
    }
    

    Create a new class ElementMappingTypeResolver and implement it as follows:

    public class ElementMappingTypeResolver : IMappingTypeResolver
    {
        private readonly ICSharpFileBuilderTemplate _template;
    
        public ElementMappingTypeResolver(ICSharpFileBuilderTemplate template)
        {
            _template = template;
        }
    
        public ICSharpMapping ResolveMappings(MappingModel mappingModel)
        {
            if (mappingModel.MappingTypeId != "ENTER ID HERE")
            {
                return null;
            }
    
            var model = mappingModel.Model;
    
            if (model.SpecializationType is "Class" || model.TypeReference?.Element?.SpecializationType == "Class")
            {
                return new ObjectInitializationMapping(mappingModel, _template);
            }
    
            if (model.SpecializationType == "Association Target End" && model.TypeReference?.IsCollection == true)
            {
                return new SelectToListMapping(mappingModel, _template);
            }
    
            return null;
        }
    }
    

    In Intent Architect, go to your Element Mapping element, left-click on it and locate the 3 dots on the right-hand side. Click on it and select Copy Id to clipboard.

    Copy Id to clipboard

    Go back to the ElementMappingTypeResolver and locate the ENTER ID HERE string. Replace it with the Id you copied in Intent Architect.

    if (mappingModel.MappingTypeId != "eba4de6c-8b26-4a4e-ab7d-48e327495227")
    {
        return null;
    }
    

    In the ElementMappingTemplatePartial constructor, add this resolver like this:

    var manager = new CSharpClassMappingManager(this);
    manager.AddMappingResolver(new ElementMappingTypeResolver(this));
    

    Build and Install the Module

    Once the custom module is set up and implemented, we'll cover the process of installing the module into your application. This step makes the new mapping capabilities available for use in the application.

    Compile the ElementMappingModule project in Visual Studio.

    Note the location of the created module in the Build log:

    Successfully created package C:\Code\MyModules\Intent.Modules\ElementMappingModule.1.0.0.imod

    Create a Clean Architecture application in Intent Architect for testing this newly created module. Call it TestApp.

    To set it up to install the custom module in the TestApp, follow the Install the Module instructions here: Install and Run the Module.

    Testing the Module

    Testing the module involves creating domain models and commands within the application and applying the mappings to ensure that the generated code correctly maps between commands and domain entities, confirming the intended functionality.

    Navigate to the Domain Designer for TestApp. Create two Classes defined like this:

    • Order
      • RefNo as string
      • CreatedDate as datetime
    • OrderLine
      • Description as string
      • Amount as decimal
      • Quantity as int

    Create an association from Order to OrderLine as a 1 -> * relationship.

    Domain Model

    Next, navigate to the Services Designer and create a Command called CreateOrderCommand. Provide it with a RefNo as string and CreatedDate as datetime fields. Last, right-click on the Command and select Add element mapping. Skip the name for the association by pressing tab to jump right into the type dropdown and select Order.

    Service Model

    Diagram on Service designer

    Right-click on the [map] : Order(...): void element and select Map To Element. This presents an advanced mapping screen where you can map the two Mapping Types: Invocation Mapping and Data Mapping.

    • Double-click on the Order class on the right-hand side to set up the Invocation Mapping represented as a dotted purple line.
    • Double-click on the RefNo and CreatedDate fields on the right-hand side to set up the Data Mappings represented as solid blue lines.
    • Double-click on the OrderLines field twice to set up the Data Mappings represented as solid blue lines.
    • Click DONE.

    Advanced mapping

    Run the Software Factory and open the ElementMapping class located in the Application project. It should look like this:

    namespace TestApp.Application
    {
        public static class ElementMapping
        {
            public static Order MapToOrder(this CreateOrderCommand source)
            {
                var result = new Order
                {
                    RefNo = source.RefNo,
                    CreatedDate = source.CreatedDate,
                    OrderLines = source.OrderLines
                        .Select(ol => new OrderLine
                        {
                            Id = ol.Id,
                            Description = ol.Description,
                            Amount = ol.Amount,
                            Quantity = ol.Quantity,
                            OrderId = ol.OrderId
                        })
                        .ToList()
                };
                return result;
            }
        }
    }
    

    This allows you to write the following code inside the CreateOrderCommandHandler class:

    public async Task Handle(CreateOrderCommand request, CancellationToken cancellationToken)
    {
        var order = request.MapToOrder();
    }
    
    • Edit this page
    ☀
    ☾
    In this article
    Back to top Copyright © 2017-, Intent Software Pte Ltd.