Raml 1.0 introduces new concept called DataType.  This fragment is used to declare type in a separate yaml file. This is not just a simplification of JSON schema to conform raml/yaml. It also brings simple improvements and syntax sugar that allows to write types in more concise form. And therefore more readable. In this article we will look at how to define own data types and reuse them in API and its implementation.

Custom data types

Anyone who have designed an API knows that it is probably always necessary to define custom data structures as simple data types are not meaningful enough. Below you can see data types that are supported by Raml 1.0. We have a group of scalar types like integer, date-only and so on. We have also compound structures like array and objects. XSD and JSON schema are a separate types as developer can still share custom types using these schema.

Data types in Raml 1.0 (source: github.com/raml-org/raml-spec/)
Data types in Raml 1.0 (source: github.com/raml-org/raml-spec/)

First we will prepare sample JSON schema and XSD. Then I will show you the processes of moving them to DataType fragments. Finally we will see how they are used in API and Mule project.

JSON Schema

In our service I would like to operate on case resource. Here is the sample object:

{
  "ID": "ABC",
  "Date": "2019-01-01",
  "Description": "I would like ... ",
  "Subject": "Query",
  "Priority": 1,
  "Type": "03"
}

Below I have attached JSON schema for this entity:

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "description": "Custom Schema",
  "definitions": {
    "date-only": {
      "type": "string",
      "pattern": "^((19|20)\\d\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$"
    },
    "case-type": {
      "type": "string",
      "enum": ["01", "02", "03"]
    },
    "case": {
      "description": "Case schema",
      "type": "object",
      "properties": {
        "ID": {
          "type": "string"
        },
        "Date": {
          "$ref": "#/definitions/date-only"
        },
        "Type": { 
          "$ref": "#/definitions/case-type" 
        },
        "Description": {
          "type": "string"
        },
        "Subject": {
          "type": "string"
        },
        "Priority": {
          "type": "number"
        }
      }
    },
    "cases": {
      "type": "array",
      "items": {
        "$ref": "#/definitions/cases"
      }
    }
  }
}

I have prepared four definitions. date-only and case-type are reusable types that can be used anywhere in the schema file. Main case entity uses both date-only and case-type. For get /cases operation we need an array of cases. That is the reason why cases type has been prepared.

XML Schema

Previously described object can be represented in XML format. Here is the example case object:

<Case>
  <ID>ABC</ID>
  <Date>2019-01-01</Date>
  <Description>I would like ...</Description>
  <Subject>Query</Subject>
  <Priority>1</Priority>
  <Type>03</Type>
</Case>

And here is XSD:

<?xml version="1.0" encoding="UTF-8"?>

<schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:tns="https://profit-online.pl/blog" targetNamespace="https://profit-online.pl/blog" version="1.0" elementFormDefault="qualified" attributeFormDefault="unqualified">

  <simpleType name="CaseType">
    <restriction base="string">
      <enumeration value="01" />
      <enumeration value="02" />
      <enumeration value="03" />
    </restriction>
  </simpleType>

  <complexType name="Case">
    <sequence>
      <element name="ID" type="string" minOccurs="0" />
      <element name="Type" type="tns:CaseType" minOccurs="0" />
      <element name="Description" type="string" minOccurs="0" />
      <element name="Date" type="date" minOccurs="0"/>
      <element name="Subject" type="string" minOccurs="0"/>
      <element name="Priority" type="number" minOccurs="0" />
    </sequence>
   </complexType>

   <complexType name="Cases">
     <sequence>
       <element name="Case" type="tns:Case" minOccurs="0" maxOccurs="unbounded"></element>
    </sequence>
   </complexType>
</schema>

Raml DataType fragment

Custom DataType can be used within your API definition or can be shared by using API fragments. Result is the same just the opportunity to reuse model is emphasized by the latter solution.

In order to create new API fragment in Design Center click + Create button and then API fragment. In the next popup windows you need to provide Project Name and Fragment Type. For my case demo it is Case Model and Data Type.

So how can I model previously shown JSON and XML representation of Case(s) ? As DataType can be serialized to both XML and JSON we do not need two separate models. However we may enrich it with tips for XML serializer. For example we may declare some property as an attribute.

We know that we need to declare an object (line 3). Then we define all available properties for it. In 6th line I have declared ID field that is optional and is of type string. This is a handy shortcut in comparison to full declaration like in lines 7-9. The reason why we did not use question mark for Date field is its data type.  We needed to declare it and it is different than string.

For XML I would like to have a custom namespace that can be declared in xml.namespace property. Apart from that, we may define example(s) either using JSON/XML raw notation or using YAML notation like in the example above. Example in YAML is converted to JSON in API Console and Anypoint Exchange.

If you compare this DataType declaration to JSON schema you will definitely see similarities. So if you are fluent with writing JSON Schema you should be able to write as well easily custom Data Types. More about DataTypes you can find in Raml 1.0 Specification.

After model has been prepared we need to publish it into an Exchange in order to use this in API specification. So how to use shared model?

Usage

First we need to create an API specification.  We can do this in Design Center. In my demo I have named my project as Case API.  It is a simple CRUD service. Here is the API without model attached.

Simple CRUD service
Simple CRUD service

As you can see we allow to perform GET and  POST methods on cases resource and GET, DELETE and PUT on {id} resource. As you may expect service will return arrays as well as single items.  In 3rd line I have declared that API will allow two media types both JSON and XML.

This is fairly simple API. Now we need to attach data model.

Applying model to API

If we have Data Types exposed as API fragments on the Exchange we need to import them as dependencies. In order to do this we need to click Exchange dependencies button on the left hand side ( graph icon).  In Dependencies window we have all API fragments that we have already imported and we can add more. In Consume API Fragment window, attached below, we can see all ours fragments and already prepared by MuleSoft. In my case I have decided to import Case Model.

Importing Exchange dependencies in Design Center
Importing Exchange dependencies in Design Center

 

Now in Dependencies view I can see one item: Case Model 1.0.0.  This implies that I have imported Case Model in version 1.0.0. Here I can change version of this API, see all files or remove dependency completely.  Behind the scene files from Case Model were copied to exchange_modules folder. In the screenshot below you can see sample structure. Folder exchange_modules is accessible only in read only mode.

Files view with imported dependency
Files view with imported dependency

Below you can see update API. In 7th line we include previously defined case-type in DataType file. Next we refer to this type as a Case (line 6). Let’s see how do we enable our service to work with both XML and JSON content just based on single data type definition.

 

Let’s start with the simple POST case operation. In line 21 we set body to Case. This is a direct simple assignment. We may define type property as a child and there assign Case type. This is longer option however it gives more flexibility to configure additional properties.  As a result we may use this for both media types.

Root element

Below you have two lines long xml document. Is it valid XML block?

<invoke-static doc:name="Generate Gender" class="com.profitonline.generators.GenderGenerator" method="generate()"/>
<invoke-static doc:name="Generate Number" class="com.profitonline.generators.NumberGenerator" method="generate()">

If you answered NO then you are right.

XML document must have only one root element.  In depicted example we have two roots called invoke-static. We may fix this by wrapping it. To wrap it we may use pluralized version of the already existing elements. In other words we may wrap it in invoke-statics.

For XML media type Case will be used as a wrapper for all elements. In lines 14 – 19 we have defined response body by media type. You may ask why I cannot do it once like before. Because you need to define a wrapper for xml format (line 18 and 19). Without this we would return array items without any wrapper around it. And now you now that this is considered as a not valid XML.

Now we are to implement it.

Implementation

During creating project you can specify API definition file. You can point to a local file or directly from Design Center.  After that, your should make some cleaning in the folder. It is a good practice to put all global configurations into a single file called global.xml. You may remove API Console if you do not need it. I also split operations into a different files.  In the diagram file you can see a demo configuration files. In resources folder you can find your imported API and dependencies from exchange.  As you can see file are direclly copied into a directory.

Project's structure
Project’s structure

 

POST cases

When you decide to expose response in more then one media type you need to declare separate private flow for each. In my case I have two flows for creating a Case like below

  • post:\cases:application\json:case-api-config
  • post:\cases:application\xml:case-api-config

In this case you should create another flow encapsulating operation logic apart from transformation. It can be done by calling private flows. As my demo case is really simple I have only transformations. For json it is straightforward as depicted below:


%dw 2.0
output application/json
---
{
  ID: payload.ID
}

For XML output it is a littile bit more complicated but still rather straightforward:


%dw 2.0
output application/xml
ns ns0 https://www.profit-online.pl/blog
---
{
  ns0#Case: {
    ns0#ID: payload.ns0#Case.ns0#ID
  }
}

The major difference is usage of namespace. I have declared namespace called ns0 to value defined in my DataType (xml.namespace). One more that is worth mentioning is the wrapper. As you remember we need a root element (here Case).

PUT and other methods with body would behave simillar. How about GETs?

GET cases

For GET we can define different media types only for response (not only, but it is recommended). We do not sent content-type header for GET calls. That is why we have only one flow. In one of my previous blog posts I described that in that situation you can distinguish type by Accept header sent by client. That is the reason why we have a choice component in the middle of the flow. It checks if header Accept has value application/xml or not. If not by default JSON output is generated.

Validation

API Kit router validate incoming messages against our DataType. It knows that we allowed both XML and Json requests and it uses our custom type for validating both. I would like to be sure if everything that I am sending back to the client is as well 100% valid against my schema. Unfortunately API Kit router does not do it.  You may ask why should we validate outgoing message. I would say, to be perfectly sure that your service what surprise your clients and you. You may make an assumption that everything will work fine but that might be not true.  From my real live experience I know that sometimes services returns responses with mistakes. It may be a human mistake but still it can occur. In this case we could use Validation Message Processor however it does not allow DataType definition.

Unique Particle Attribution

When I sent XML request to my application I received peculiar error message about Unqiue Particile Attribution:

Error validating XML. Error: cos-nonambig: "https://www.profit-online.pl/blog":ID and WC[##any] (or elements from their substitution group) violate "Unique Particle Attribution". During validation against this schema, ambiguity would be created for those two particles.

I suspected that I have some errors in my DataType and I was right. It turned out that by mistake I have written someting like that:

properties:
  ID?:
  Date?:
    type: date-only

You may see what is wrong here. At 3rd line I have made a mistake. We are not supposed to define further Date property if we use question mark.

Summary

DataType is really good way to define your custom type in a concise way. Semantic is similar to JSON Schema but with improvements. It is very impressing that I can using one DataType  with different media types. It safes time and mitigates the risk that your schemas won’t match. However we can’t reuse it in Validation Message Processor as it only accepts JSON Schema ans XSL.

Tagged on:     

One thought on “Reuse driven by DataType

  • 03/07/2018 at 10:55
    Permalink

    Thank you very much for this article. It explains a lot.
    Dziekuje i pozdrawiam.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *