November 1 2019

System API's are a part of MuleSoft’s three-layer approach to integration in an application network. This blog post is working off the assumption that the reader has some familiarity with this approach. If not, you can find more information at API Led Connectivity and API Led Connectivity 101.

As a general rule, the straight pass-through system API's are of little use. In six years of consulting in MuleSoft and ten plus overall, this is a very common mistake many people make, especially by those new to integration. In my opinion, System API’s have two uses:

  1. 1. Decoupling from the underlying system.
  2. 2. Fixing the poo of the underlying system.

Poo, in our context, means poorly written APIs. This may mean the API is hard to use, uses poor naming standards, does not use proper date/datetime formats, or may even use different formats in the same API… If you come from an OOP background, think system API's in terms of patterns such as Adapter and Facade.

I’m currently integrating with a 3rd Party API that is flat out terrible. It does serve as an excellent example of some common scenarios that are overlooked and in desperate need of cleanup.

 

Poor 3rd Party API

The following is an example of a 3rd party “REST” API I’ve recently had to consume. It’s in desperate need of fixing:

"Delete a single entity record." 
GET:  /api/v2/entity/{entityName}/delete/{rowUID}
“List entity records”
GET:  /api/v2/entity/{entityName}/list
“Fetch a single entity record.”
GET:  /api/v2/entity/{entityName}/fetch/{rowUID}
“Add a single entity record.”
POST: /api/v2/entity/{entityName}/add
“Update a single entity record.”
POST: /api/v2/entity/{entityName}/update

 

Especially egregious is the “Delete” that uses the HTTP method GET. It’s neither safe nor idempotent, which GET must be.

Additionally, it uses resources to indicate what it’s doing instead of the proper HTTP method. It’s also quite hard to use as you have to call other API's now to know what the possibilities are for the entityName. 

While just exposing the API, as-is would be much faster. I chose to do the extra work to make it far easier to work with. I made the system API look like:

DELETE: /api/roles/{roleId}
GET: /api/roles
GET: /api/roles/{roleId}
POST: /api/roles
PUT: /api/roles/{roleId}

 

I also applied this to all the other entities required.

 

Inconsistent Naming

In 3rd party APIs, it’s not uncommon to be presented with inconsistent naming of resources and attributes. In my case, the API mentioned above exposed the following to get a list of roles:

GET:  /api/v2/entity/Role/list

 

Notice they also didn’t favor using plurals for resource names. I chose to just fix this by exposing and implementing the resource:

GET: /api/roles

 

Additional items you need to look at is the actual data. You often need to change JSON attribute names to match your companies naming standards or to have them make sense. 

Here’s a manufactured example based off a few APIs over the years:

{
	"fn": "Paul",
	"ln": "Smith",
	"c": "Canadian",
	"s": "OK",
	"ma": "12342 Some St",
	"zip-code": "12345"
}

 

This is yet another thing we should fix in our system API. While not an issue in this example. We can also take the time to group things together, which often occurs when dealing with ERP systems and non-normalized databases. Here is an arbitrary DataWeave example to fix retrieving information from the 3rd party API.

%dw 2.0
output application/json
---
{
   firstName: payload.FIRST_NAME,
   lastName: payload.givenName,
   mailingAddress: {
       street: payload.ma,
       county: payload.c,
       state: payload.s,
       zipCode: payload.'zip-code'
   }
}

 

Output:

{
 "firstName": "Paul",
 "lastName": "Smith",
 "mailingAddress": {
   "street": "12342 Some St",
   "county": "Canadian",
   "state": "OK",
   "zipCode": "12345"
 }
}

 

Nonstandard Date/Date Time

Other items I often need to fix in system API's are dates and datetimes. If the system doesn’t use ISO 8601 dates, fix it. Do not spread poo throughout your other systems. It’s quite simple to fix dates/datetimes and convert between them in your system API with DataWeave. A DateTime without time zone information is evil. One should not need to reference documentation to figure out what time zone is being used.

 

Adding Time Zone Info

In this same API, they returned dates in the following format, 2019-12-05T12:31:00.234, notice the complete lack of time zone information. The documentation didn’t mention what time zone these are in, and the programs gui would always show it in the logged-in user's time zone. After experimentation, it was determined the system stored all date times in UTC.

This is a rather easy fix. They stored a local date-time, and since we figured out where they consider local to be, we have something to work with. Note: In all of our examples, we are going to just hard code in our inputs via a variable, and pick some arbitrary output for demonstration purposes.

%dw 2.0
output application/json
var dateTime = "2019-12-05T12:31:00.234"
---
{
 originalDateTime: dateTime,
 fixedDateTime: (dateTime as LocalDateTime {format: "yyyy-MM-dd'T'HH:mm:ss.SSS"}) ++ "UTC"
}

 

Output:

{
 "originalDateTime": "2019-12-05T12:31:00.234",
 "fixedDateTime": "2019-12-05T12:31:00.234Z"
}

 

Here we are simply reading our DateTime into a LocalDate time via:

dateTime as LocalDateTime {format: "yyyy-MM-dd'T'HH:mm:ss.SSS"}

 

Next, we concatenate on the time zone information:

++ "UTC"

 

If you aren’t familiar with format in DataWeave and your options, DataWeave just uses the java DateTimeFormatter. You will find them defined under the Patterns for Formatting and Parsing section. Word of Caution: if you have no idea what a week-based-year is, you do not want to use Y, you want to use y which is a year-of-era

You may find the various available time zone id’s or via following DataWeave by utilizing calling java static methods from dataweave:

%dw 2.0
output application/json
import java!java::util::TimeZone
---
TimeZone::getAvailableIDs()

 

Switching Time Zones

Now, that we have DataWeave to fix our datetimes and expose them as something more useful. How do we convert them back?

%dw 2.0
output application/json
var dateTimeWithTZ = |2019-12-05T12:31:00.234Z|
---
{
   backToLocalDateTime: (dateTimeWithTZ as String {format: "yyyy-MM-dd'T'HH:mm:ss.SSS"})
}

 

Output:

{
 "backToLocalDateTime": "2019-12-05T12:31:00.234"
}

 

We have already significantly improved the user's experience, but we can make it better. Out DataWeave always assumes the time zone we are given is in UTC. Why don’t we just accept any valid time zone and then convert that to UTC?

%dw 2.0
output application/json
var dateTimeInCDT = |2019-12-05T12:31:00.234-06:00|
---
{
   backToLocalDateTime: (dateTimeInCDT >> "UTC") as String {format: "yyyy-MM-dd'T'HH:mm:ss.SSS"}
}

 

Output:

{
 "backToLocalDateTime": "2019-12-05T18:31:00.234"
}

 

The key here is using >> when we have a DateTime to shift our date time to any time zone.

 

Summation

Use system API's to fix the health hazard that is unencapsulated poo. If we don’t, we only pile on more technical debt.

  • Fix hard to use API's and poorly written API's
  • Only expose ISO 8601 date/datetimes. Adjust to whatever the system requires behind the scenes
  • Use ++ to append time zone data to LocalDateTimes and >>to convert DateTimes from one time zone to another.

About the Author

Bio

Paul has twenty years of experience in full stack software development, and over six with MuleSoft.  He has developed, architected, and supported MuleSoft solutions in banking, financial services, online and brick and mortar retail, software as a service, manufacturing, pharmaceutical, insurance, and Oil and Gas industries.  His areas of expertise include MuleSoft 3 and 4 and Java.

Join the Conversation

Enter your first name. It will only be used to display with your comment.
Enter your email. This will be used to validate you as a real user but will NOT be displayed with the comment.