Practical .NET

Supporting Developers with JSON Schema

If you're building services it's critical that you support the developers who will create and read your service's messages. JSON Schema lets you support the three principles of good message design in a way that supports developers.

When you're designing messages to work with your service, there are three principles you should follow:

  1. Define as few message formats as possible: This increases the odds that one service's output message is another service's input message.
  2. Make all request messages as simple as possible: Write your services so that, when processing a request message, they take all reasonable defaults and ignore "extra" information.
  3. Have response messages return as much data as possible: When designing a response message, think "business transaction document" rather than "third normal form data."

These rules have multiple benefits: They simplify life for developers, reduce maintenance costs, improve scalability, make for responsive applications, and make it easier to create new applications by stitching together existing services (I've discussed the scalability/responsiveness claims and the maintenance/development claims elsewhere).

In my last column, I introduced JSON Schema as a way to implement those principles by defining a core message format and extending that format for specific messages. As an example, the following code shows a base salesOrder definition that's extended with additional properties to support a response message:

"definitions": {
    "salesOrder": {
      "type": "object",
      "properties": {
        "customerId": { "type": "string" },
        "orderId": { "type": "string" },
        "orderDate": { "type": "string" }
     }
   },

   "salesOrderResponse": {
      "allOf": [
         { "$ref": "#/definitions/salesOrder" },
         "properties":{
            "validationCode": { "type": "number" },
            "validationMessage": { "type": "string" }
         }
      ]
   }

Communicating Requirements
However, the default for JSON schema is that all properties are optional. Effectively, my sample design tells the developer that every property is optional ... which isn't the case for any particular message. In my sample response message, the validationCode and validationMessage properties must be provided, for example. Only if I specify in the schema that those two properties are required is it worthwhile to use the schema in development (to ensure that I'm creating my response messages correctly) and in production (to determine that any incoming messages I'm receiving are correct). For more on how to validate messages in code, see my earlier article.

The problem is that, in my base salesOrder schema, all the properties are optional ... in some messages. Even the orderId property is optional because it shouldn't be provided in the message used when adding a sales order (the SalesOrder service will generate and provide the orderId when adding a SalesOrder).

Fortunately, I can add the required keyword to the allOf array in my salesOrderResponseMessage to specify that the two validation-related properties are required. The following code shows my salesResponseMessage, with that additional requirement:

"salesOrderResponse": {
  "allOf": [
    { "$ref": "#/definitions/salesOrder" },
    {
      "properties": {
               "validationCode": { "type": "number" },
               "validationMessage": { "type": "string" }
      },
      "required": [ "validationCode", "validationMessage" ]
    }
  ]
}

I can also use the required keyword to specify required properties in the base message format. This example specifies that, in a message for deleting a salesOrder, the orderId and customerId properties are required:

"deleteSalesOrder": {
      "allOf": [
         { "$ref": "#/definitions/salesOrder" },
         {
            "required": [ "orderId", "customerId" ]
         }
      ]
   }

While you could define all the message formats in a single schema file, if your tools support defining schemas in separate files, then that's the path you should take. While it would make it convenient to find all the variations on your base message if they're all in one file, that design would make validating messages against any particular schema more difficult. If you pass a message and a schema file containing multiple schemas to the validation process and the process returns a true, you have to ask which of the schemas your message is valid against -- the salesOrderResponse or the deleteSalesOrder?

You can, in theory, use $ref to refer to schemas in other files. However, the file reference is relative to the URI in your schema's id property, which isn't really convenient (you probably want to refer to another schema in the same project). As an example, the ajv package (ajv for "Another JSON Schema Validator") provides an addSchema method to support this kind of multifile design.

By the way, in addition to allOf you can also use:

  • anyOf when zero or more of the fragments may apply. A SalesOrder might have some combination of gift cards or in-house credits applied to it.
  • oneOf when only one of multiple exclusive options apply. A SalesOrder might only allow only one of a special sale price or a discount to be applied to it.

While I've emphasized "communicating to other developers" in this column, six months from now, you will be that "other developer." Leveraging JSON Schema to define and communicate messages that follow the three principles is not only good for others, it's good for you too.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

Subscribe on YouTube