Fast track – From Sonata API definition to running code

Oct 7, 2019 | Blog

→ Join Amartus at the MEF19 event, Los Angeles 18-20th November 2019 | Booth 514

Fast track – From Sonata API definition to running code

by Bartosz Michalik, Principal Architect at Amartus

 

Intro

As you might (or might not) have heard, MEF has released a developer version of LSO Sonata APIs. Currently, MEF is working on the next version of the APIs that should be available at the end of this year.

Sonata APIs are based on TM Forum APIs which come (for good or bad) with majority of the inherent design decisions of TMF APIs. But that could be a full blog post on its own. Here I would like to walk developers through the process of building code that is compliant with MEF APIs.

Let me start with two assumptions:

  • The reader would like to start building solutions that are compliant with the latest version of Sonata API, which is publicly available here.
  • The reader is prepared (technically and mentally) to read code samples, which, for the sake of argument, come from the Java ecosystem.

If these conditions are met, I promise that at the end of the article the readers will learn how to get a very basic server code up and running. At least for one API endpoint since I plan to use ordering API through this blog post.

Quick start

 

Sonata SDK contains API structure definitions (Swagger 2.0 format is in use) and specific product descriptors (JsonSchema is in use here). Currently, the structure of Ethernet Virtual Private Line (E-line) and UNI specifications are available.

Dedicated users of Swagger tools will have their preferred code generators to build the client or server code binding for a specific language/framework.

Let’s try to do it in the simplest possible way:

  1. Clone or download Sonata SDK from MEF github
  2. Go to http://editor.swagger.io/
  3. Copy and paste api/ProductOrder/MEF_api_productOrderManagement_2.0.0.yaml
  4. Generate code bindings project – I have chosen server site code using Spring Framework

Will it work? Well, it depends on the definition of “working.” The process generates all the code bindings for the API, including some basic validations. And, the server code is fully functional – meaning it’s possible to trigger REST requests against it and it will respond with some not very useful payloads:

  • it will complain about sending payloads not aligned with Swagger schema;
  • it will ignore (this might be specific to my implementation) extra attributes that are not recognized.

However, the code has no representation of the MEF specific payloads. How to fix that? Let me explain.

MEF Sonata API vs. TMF API definitions

 

MEF Sonata APIs are inspired by TMF Open APIs (in our example, the API is based on TMF622). I say that they are inspired as during the MEF standardization process it was discovered that some of the constructs proposed in TMF specification are hard to implement and these were modified. My understanding is that currently MEF is contributing these findings back to TMF.

TMF 622 is an abstract construct that provides a number of ways to manage product orders, but for it  to be useful, it needs to be extended. In reality, the developer must trade specific products. So this is how MEF is building on top of TMF APIs – it adds MEF definitions based on MEF standards to the picture (and modifies TMF constructs where they are not working).

In the TMF world, there are two ways of extending concepts: by implementing characteristic patterns and/or by using extension patterns. MEF is using the latter option to introduce its definitions.

Extension pattern is introduced to support inheritance (one of the core concepts of object-oriented modeling). Basically, a bunch of additional attributes can be introduced to any type represented in API definition, and then add meta-information attributes (e.g. @type, @schemaLocation, @baseType) that say how to interpret these attributes at runtime. Great, right? Well, given the issues we have with the code so far, this is not really helpful. Swagger code generation tools have no idea about these meta-attributes.

Moreover, they are considered as runtime binding concepts. Is there any hope? Well, in the same document we read that “In Swagger 2.0 and 3.0 allOf SHOULD be used to extend an object.” Let’s give it a try.

MEF Product Specifications

 

Sonata SDK in its current release contains specifications of UNI and E-line products. There is more to come, so with time all MEF standard services might be available as Sonata-level definitions.

These specifications are located in payload_descriptions/ProductSpecDescription in the repository. By looking at some of the definitions in that directory, it is possible to learn that it is a json-schema. Answering which version of the json-schema it is encoded with is as challenging as understanding json-schema standardization itself. I was trying to just push it through quicktype.io which I picked from the code generation tools listed at the json-schema landing page, and the result was not aligned with what I had expected. So clearly, either the tool is broken, or the definition is not aligned with one of the supported json-schema drafts. The latest meta-schema version is 2019-09, which corresponds to draft-handrews-json-schema*-02, which is also referred to as draft-8. Is this confusing enough? Actually, there is a page that may be helpful to understand how to deal with json-schemas – “understanding JSON Schema”.

Approach 1 – use json-schema as generation source

 

My approach was to align MEF product specification with draft 7, which seemed to give the expected results. And it was rather simple – I just removed one level of definitions like this:

What can be done next:

  1. Make the MEF product descriptions align with a json-schema version.
  2. Use a tool to generate code.
  3. Manually integrate the generated classes with Sonata bindings that were generated from Swagger.

The last step is the most problematic one because it requires performing a number of small modifications in the generated code base. But depending on the use case, it might be sufficient.

Approach 2 – generate code using Swagger

 

But maybe we can use Swagger generators to do all of the job for us? Well, if we use the TMF extension pattern, we could try to use allOf composition keyword as suggested.

The first difficulty is that we do not have our payloads in Swagger format. Fortunately, Swagger is, to some degree, compliant with json-schema. More precisely, a Swagger definition uses a subset of json-schema keywords defined in draft-5.

So as in approach 1, the first step is to fix MEF definitions to be valid json-schema definitions. Then we might try to align it to one encoding – either json or YAML and merge. I prefer the latter; thus, I have used the online “json2yaml” tool for conversion. Then given there a single root object, I moved the top-level definition of UNISpec into definitions sections. In the last step, I merged all definitions into Sonata Product order management file. Last but not least, we need to make UNISpec a composed structure that consists of product and its own attributes. And…

Success. We have a representation of MEF Product in the Sonata data model. Our UNISpec is extending product in the code:

public class UNISpec extends Product {
@JsonProperty(“physicalLayer”)
@Valid
private List<PhysicalLayer> physicalLayer = new ArrayList<PhysicalLayer>();

Unfortunately, it does not work at runtime. When an object is deserialized, there is no way the tooling will be able to figure out to which concrete class in hierarchy bind the payload. Is there any hope?

In the Swagger specification there is a paragraph discussing polymorphism modeling that explains a requirement to indicate which field is used as discriminator. So, we need to configure discriminator property for Product type. Then, to keep the TMF semantic of meta-fields, we can use @type attribute.

To summarize the steps in described approach:

    1. Configure discriminator value on the product entity in product ordering API
    2. Make the product definition a valid json-schema descriptor
    3. Convert product description to YAML format and merge into API swagger
    4. Make the specific MEF product by inheriting from generic product
    5. Generate the code bindings

As a result, the inheritance relation between product and the UNISpec should be evident in addition to the appropriate json annotations that define deserialization hints. The code will look like this:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = “@type”, visible = true )
@JsonSubTypes({
@JsonSubTypes.Type(value = UNISpec.class, name = “UNISpec”),
})
public class Product {

Closing remarks

I hope that after reading this article, developers will be able to generate all the code bindings needed for building successful solutions for Sonata. Unfortunately, right now there is no perfect solution that works out of the box. But in MEF, we are working on making adoption of Sonata as seamless as possible. Sonata API definition is a community effort, thus any ideas on how to improve the definitions or delivery process are more than welcome.

 

Would you like to know more about what we do?