Key components

We will start by looking at the framework and its components. The key components of a document service are as follows:

  • A query that is used in the AIF Document Service Wizard to create the document service
  • A document class that represents a business entity and contains the business logic for this entity
  • One or more AxBC classes that encapsulate a table and are used by the document class to create, modify, and delete data in tables
  • A service class that contains the service's operations

Of course, there's more to these components than the few words we've used here to describe them. We will now look at these components one by one, starting with the query.

The document query

Each document service is based on a query defined in the AOT. Using the AIF Document Service Wizard, a document class is generated and XML schema definitions that are used for the XML serialization are derived from the corresponding query. Therefore, the XML message will have a correlation to the query object. In the following screenshot, we can see the query for the InventItemService document service:

The document query

If we look at an actual XML message, we can clearly see that it matches the structure of the query object. The following screenshot shows us the XML content after the serialization of the item's business object:

The document query

The document class

Document classes extend the AxdBase class and represent a business document, for example, a sales order. They contain the business logic across all of the tables that correspond with the document. Hence, the details about the underlying tables are hidden from the consumer.

A document class also handles the generation of XML Schema Definition (XSD). The XSD schema defines the document structure and the business rules that are to be followed. Along with the generation of XSD, the document class also contains logic to serialize the table entity's classes into XML and deserialize them from XML.

Responsibilities of a document class

Document classes have a number of responsibilities, and among them, we have the following:

  • Generating an XSD schema.
  • Serializing and deserializing classes to and from XML.
  • Guaranteeing the document life cycle by making sure that operations do not violate business rules that correspond with the document.
  • Containing business logic that applies to data across tables.
  • Providing a means to define the document-level properties. For example, whether a document is an original or a duplicate.
  • Handling consolidation of table-level errors and returning them as a single list to the calling code.

Let's look at an example document class and analyze it to see how some of these responsibilities are actually handled. In this example, we will take a closer look at the sales order document class, AxdSalesTable.

XSD generation

XSD is generated in the getSchema() and getSchemaInternal() methods of the AxdBase class. The AxdBaseGenerationXSD class is called to generate XSD based on this document class and its underlying table classes, as shown in the following code snippet:

private AifDocumentSchemaXml getSchemaInternal(Boolean _includeLabels, container _languageIds)
{
   AxdBaseGenerateXSD genXsd;
   str documentClass ;
   AifDocumentSchemaXml schemaXml;
   genXsd = AxdBaseGenerateXSD::construct();
   genXsd.parmIncludeLabels(_includeLabels);
   genXsd.parmLanguageIds(_languageIds);
   documentClass = new SysDictClass(classIdGet(this)).name() ;

   genXsd.setSharedTypesSchema(sharedTypesSchema);
   schemaXml = genXsd.generate(documentClass,this.getName(),
   this.getQuery());
   sharedTypesSchema = genXsd.getSharedTypesSchema();

   return schemaXml;
}

XML serialization and deserialization

XML serialization and deserialization is performed in several places depending on the operation that is being executed. Either the axdBaseRead, axdBaseUpdate, or axdBaseCreate class is used to service the consumer's call. For example, take a look at the axdBaseRead class that is used when performing a read operation. Here, you can find the serializeDocument()method that is used to serialize the document into XML, as shown in the following code snippet.

protected AifDocumentXml serializeDocument(AifConstraintListCollection _constraintListCollection, boolean _calledFromRead)
{
   ClassName documentName;
   Map propertyInfoMap;
   this.init();

   this.setDocumentNameSpace();

   documentName = axdBase.getName();
   propertyInfoMap = this.getMethodInfoMap(classIdGet(axdBase));

   axdXmlWriter.writeStartDocument(documentName);

   this.serializeClass(propertyInfoMap, axdBase);

   // Omitted code

   axdXmlWriter.writeEndDocument();
   return axdXmlWriter.getXML();
}

In the preceding code, the following methods are used:

  • getMethodInfoMap() fetches all of the fields for the document class
  • writeStartDocument() writes the XML document's begin tag
  • serializeClass() takes care of serializing all of the properties into XML
  • writeEndDocument() writes the XML document's end tag

Cross-table business logic

The AxdSalesOrder document class contains logic to handle cross-table dependencies. The prepareForSave() method is an example of this. This method is called for every record that is saved. Let's take a look at a small piece of the code that is used for the sales document and see how it handles logic across the SalesLine and InventDim tables:

case classNum(AxInventDim) :
   axInventDim = _axdStack.top();
   axSalesLine = axInventDim.parentAxBC();
   axSalesLine.axInventDim().resetInternalValues();

   if (createRecord)
   {
      axSalesLine.salesLine().unLinkAgreementLine();
   }
   else
   {
      //InventDimId marked as touched in update scenarios and we need 
      //new InventDimId
      axSalesLine.clearField(fieldNum(SalesLine,InventDimId),false);
   }

   axInventDim.moveAxInventDim(axSalesLine.axInventDim());
   axSalesLine.setInventDimIdDirtySaved(false);

   return true;

In the code, we can see the following:

  • In the case of an insert, the link with any possible agreement lines is removed
  • In the case of an update, the current InventDimId field is blanked out so a new InventDimId can be filled in
  • Lastly, the values of the InventDim table class are copied to the SalesLine table class, and the InventDim field of SalesLine is marked as dirty to be saved

Validation and business rule enforcement

The document class is also responsible for validating the business document and making sure that the business rules are enforced. An example of this can be found in the checkSalesLine()method. This method is called from within the prepareForSave()method to ensure that the SalesLine record does not contain any values that conflict with the business rules. The following code snippet shows us how two of the business rules are validated:

salesLineOrig = _axSalesLine.salesLine().orig();
if (salesLineOrig.LineDeliveryType == LineDeliveryType::DeliveryLine)
{
   if (_axSalesLine.parmSalesQty() != salesLineOrig.SalesQty)
   {
      // It is not allowed to change quantity on delivery schedule 
      // order lines.
      error("@SYS133823");
   }
   if (_axSalesLine.parmSalesUnit() != salesLineOrig.SalesUnit)
   {
      // It is not allowed to change sales unit on delivery schedule 
      // order lines.
      error("@SYS133824");
   }
}

The code checks the SalesQty and SalesUnit fields when LineDeliveryType is DeliveryLine. If these fields do not match, an error is written to the Infolog.

AxBC classes

AxBC classes can be seen as wrapper classes for tables as they provide an object interface to the underlying table. They manage data access from and to the underlying table and contain business logic that is otherwise contained on forms. They also provide a means to specify default values for fields. Another name for AxBC classes is Ax<Table> classes.

AxBC classes are optional. It is possible to have a document service in which the underlying tables have no corresponding AxBC classes. If so, the framework will use the AxCommon class to perform read and write operations to the table. In this case, you will have to place your code in the Axd<Document> class in the prepareForSave() and prepareForSaveExtended() methods.

One example that shows how AxBC classes are optional is value mapping. Value mapping can be set up in the processing options of an integration port. When this type of value mapping suffices, it is not necessary to create an AxBC class for value mapping purposes. In this case, an AxBC class becomes necessary only if you want to perform more elaborate value mapping than the standard setup allows you to.

Therefore, depending on what your needs are, you can choose not to create AxBC classes for the tables in your document service, or you can create them using the AIF Document Service Wizard. The wizard creates the AxBC classes only if the Generate AxBC classes option is selected.

Responsibilities of an AxBC class

The following are the responsibilities of an AxBC class:

  • Performing validation: AxBC classes make sure that all of the rules and logic contained in the underlying table are adhered to. Things such as data integrity and business rules defined on the field level are also maintained.
  • Providing field sequencing: Using AxBC classes, you can specify the order in which fields are processed. This is particularly useful when the value of one field depends upon the value of another.
  • Performing value mapping: Values can be mapped between external systems and Microsoft Dynamics AX 2012. Value mapping can be performed at the AxBC level if the possibilities provided by value mapping at the integration port are insufficient.
  • Enabling value defaulting for fields: Fields that are not set by the calling code and do not receive a default value in the initValue() method of the table can be defaulted in the AxBC class.

Performing validation

In the AxSalesLine class, we can see that there is validation logic in the validateWrite() method, as shown in the following code snippet:

protected void validateWrite()
{
   if (this.validateInput())
   {
      if (!salesLine.validateWrite(true))
      {
         if (continueOnError)
         {
            error("@SYS98197");
         }
         else
         {
            throw error("@SYS23020");
         }
      }
   }
}

The code shows us that the AxSalesLine class also calls the validateWrite() method on the underlying table. This is done to make sure that the validation rules on the table are adhered to.

Providing field sequencing

In almost all AxBC classes, you will find a method called setTableFields(). In the AxSalesLine class, this method calls all of the setter methods present for the fields of the SalesLine table, as shown in the following code snippet:

protected void setTableFields()
{
   //<GMX>
   #ISOCountryRegionCodes
   /</GMX>
   super();
   useMapPolicy = false;
   this.setAddressRefTableId();
   [...]
   this.setCustAccount();
   this.setCustGroup();
   [...]

When you want to define the order in which the fields are set, you can modify the code and rearrange the setter methods into the sequence that you want. In the preceding code, you can see that the CustAccount field is set first and then the CustGroup field is set. This is because determining the CustGroup field depends on the value of the CustAccount field.

Performing value mapping

If we look at the valueMapDependingFields()method, we will see an example of how value mapping can be performed, as shown in the following code snippet:

protected void valueMapDependingFields()
{
   ItemId valueMapedItemId;
   InventDim valueMapedInventDim;
   
   if (this.valueMappingInbound())
   {
      if (salesLine.CustAccount && item)
      {
         [valueMapedItemId,valueMapedInventDim] = this.axSalesItemId(salesLine.CustAccount,item);
      this.parmItemId(valueMapedItemId);
         if (!InventDim::isInventDimEqualProductDim(EcoResProductDimGroupSetup::newItemId(salesLine.ItemId), valueMapedInventDim,InventDim::find(InventDim::inventDimIdBlank())))
         {
            axInventDim.productDimensions(valueMapedInventDim);
            this.parmInventDimId(InventDim::findOrCreate(axInventDim.inventDim()).InventDimId);
         }
      }
   }
}

The exact implementation of the previous code is unimportant, but you can clearly see that the axSalesItemId()method performs the value mapping to determine the item's number. Then, the mapped item number is used on the SalesLine record. Apart from the value mapping of the item number, a mapping for the inventory dimensions of the corresponding InventDim record is also performed.

Setting default values

AxBC classes can also contain logic that sets default values on fields. An example of this is found in the setLineNum()method, as shown in the following code snippet:

protected void setLineNum()
{
   if (this.isMethodExecuted(funcName(), fieldNum(SalesLine, LineNum)))
   {
      return;
   }
   this.setSalesId();
   
   if (this.isFieldSet(fieldNum(SalesLine, SalesId)))
   {
      if (!lineNum)
      {
         lineNum = SalesLine::lastLineNum(this.parmSalesId());
      }
      lineNum += 1;

      this.parmLineNum(lineNum);
   }
}

In the previous code, we can see the following:

  • Firstly, the framework checks whether this method has already been executed
  • The setSalesId() method makes sure that the sales order number's value is set
  • If no line number is provided, the lastLineNum() method is used to determine the highest line number used at the time
  • Lastly, the line number is incremented and set in the SalesLine record

The service class

Service classes are classes that contain the operations used in the integration port of that document service. Only the operations needed by the business are available. All service classes extend the AifDocumentService class and delegate their operations to the AifDocumentService class. For example, when the Read operation is available on a service class, the implementation of the operation will call the ReadList() method on the AifDocumentService parent class.

The following operations are available:

  • Create: This operation receives a document class and creates records as and when required. The return value is an AifEntityKeyList object that contains a list of key/value pairs that reference a record.
  • Delete: This operation is used to delete records from the database. The IDs of the records to be deleted are passed as a parameter.
  • Find: This operation takes an AifQueryCriteria parameter and queries the database. The return value is a document class that contains the resulting records.
  • Findkeys: This operation does the same thing as the find operation but returns an AifEntityKeyList object, which contains only the IDs of the resulting records instead of all of the data.
  • Read: This operation takes an AifEntityKeyList object as a parameter, reads the records from the database, and returns them in a document. This operation is typically used in combination with the FindKeys operation that first returns the values that are needed as an input for the Read operation.
  • Update: This operation takes an AifEntityKeyList object that contains the IDs of the records to be updated. The second parameter is the document that contains the updated records.
  • GetKeys: This operation uses a document filter and returns the resulting document keys in an AifEntityKeyList object.
  • GetChangedKeys: This operation also uses a document filter along with a DateTime parameter to return the document keys of the documents that have changed.

The service node

For our service operations to be available in the inbound and outbound port forms, a service class alone is not enough. The services framework requires that you create a service node in the AOT for the service and its service operations that you want to expose. This is true for document services but applies equally to custom services.

A service node allows you to create service contracts based on service classes in a flexible and customizable way. If you wish, you can create multiple service nodes for one service class, each with a different external name and a different set of service operations that are exposed. You can even specify the namespace for the service and change the names of the service operations.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.42.128