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

  • Hands On: New VS Code Insiders Build Creates Web Page from Image in Seconds

    New Vision support with GitHub Copilot in the latest Visual Studio Code Insiders build takes a user-supplied mockup image and creates a web page from it in seconds, handling all the HTML and CSS.

  • Naive Bayes Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the naive Bayes regression technique, where the goal is to predict a single numeric value. Compared to other machine learning regression techniques, naive Bayes regression is usually less accurate, but is simple, easy to implement and customize, works on both large and small datasets, is highly interpretable, and doesn't require tuning any hyperparameters.

  • VS Code Copilot Previews New GPT-4o AI Code Completion Model

    The 4o upgrade includes additional training on more than 275,000 high-quality public repositories in over 30 popular programming languages, said Microsoft-owned GitHub, which created the original "AI pair programmer" years ago.

  • Microsoft's Rust Embrace Continues with Azure SDK Beta

    "Rust's strong type system and ownership model help prevent common programming errors such as null pointer dereferencing and buffer overflows, leading to more secure and stable code."

  • Xcode IDE from Microsoft Archrival Apple Gets Copilot AI

    Just after expanding the reach of its Copilot AI coding assistant to the open-source Eclipse IDE, Microsoft showcased how it's going even further, providing details about a preview version for the Xcode IDE from archrival Apple.

Subscribe on YouTube

Upcoming Training Events