days
-2
-6
hours
-1
-9
minutes
-1
-7
seconds
0
-7
Making Java development more appealing

REST API Vision with Manifold

Scott McKinney
© Shutterstock / Nomad_Soul

Discover how to use Manifold to enable JSON Schema as your REST API single source of truth (SSoT). Scott McKinney explains how the new Manifold framework connects your JSON Schema and YAML resources directly to Java without code generators, annotated POJOs, or other go-betweens.

The REST API design problem

Most REST APIs are described in terms of the request/response formats they comprise using a schema definition language such as JSON Schema. A complete REST API schema provides the following information:

  • Language-neutral type definitions
  • Formal type and data constraints
  • Comprehensive documentation

As a language neutral format, however, JSON Schema is not directly accessible from Java; the schema-defined type definitions are not visible to Java’s type system. The preferred, albeit decades-old, solution to this kind of problem involves wedging a code generator into the build to transform structured data to Java. But this trades one big problem for another — a code generation step most often hinders what could otherwise be a streamlined development experience. The problems involved with code generators include:

  • No feedback: JSON schema changes are invisible to Java until you rebuild the API
  • Stale generated classes
  • Increased build times
  • Scalability issues: code gen interdependencies, caching, integrations, etc.
  • Difficult problems with custom class loaders, runtime agents, annotation processors
  • Interrupts train of thought
  • Poor IDE integration:
    * No immediate feedback from changes
    * No incremental compilation
    * Can’t navigate from code reference to corresponding JSON schema element
    * Can’t find code usages from JSON schema elements
    * Can’t refactor / rename JSON schema elements
    Serverless Architecture Whitepaper

    LIVING IN A POST-CONTAINER WORLD
    Free: Brand new Serverless Architecture Whitepaper

    Stay on top of the latest trends with the help of our Serverless Architecture Whitepaper. Get deep insights on serverless platforms, cloud-native architecture, cloud services, the Kubernetes ecosystem (Knative, Istio etc.) and much more!

The REST API design solution

A programming language fantasy world would have the Java compiler directly understand JSON Schema and eliminate the code generation step, similar to the way metaprogramming magically connects code with structured data in dynamic languages. This is precisely what Manifold accomplishes, all without sacrificing type-safety. Manifold is a general-purpose framework to type-safely extend Java’s type system. Building on this framework Manifold also provides support for several specific data formats including JSON, JSON Schema, YAML, and others. With Manifold Java has full-spectrum JSON Schema vision:

  • The Java compiler understands JSON Schema type definitions
  • JSON Schema is the API Single Source of Truth (SSoT)
  • Eliminates the code generation step in your build!
  • Scalable: JSON files are source files
  • No custom class loaders, no runtime agents, no annotation processors
  • Top-notch IDE integration:
    * Make JSON Schema changes and immediately use the changes in your code, no compiling
    Incremental, processes only the JSON Schema changes you’ve made, faster builds
    Navigate from a code reference to a JSON Schema element
    * Perform usage searches on JSON Schema elements to find code references
    Rename / refactor Schema elements
    Hotswap debugging support

Using Manifold in your project is easy. Simply add the Manifold jar to your project and begin taking advantage of it.

The IDE experience is paramount as most professional Java developers live and breathe within the IDE. Manifold does not disappoint, it provides a complete, seamless dev experience with the IntelliJ IDEA Manifold plugin. You can install it directly from within IntelliJ IDEA:
Settings ➜ Plugins ➜ Browse repositories ➜ search: Manifold

Seeing is believing

Drop the JSON Schema file com/example/api/User.json into your resourcesdirectory:

{
  "$id": "https://example.com/restapi/User.json",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "User",
  "description": "A simple user type to uniquely identify a secure account",
  "type": "object",
  "definitions": {
    "Gender": {"enum": ["male", "female"]}
  },
  "properties": {
    "id": {
      "description": "Uniquely identifies a user",
      "type": "string",
      "format": "email",
      "readOnly": true
    },
    "password": {
      "description": "The user's password",
      "type": "string",
      "format": "password",
      "writeOnly": true
    },
    "name": {
      "type": "string",
      "description": "A public name for the user",
      "maxLength": 128
    },
    "dob": {
      "type": "string",
      "description": "The user's date of birth",
      "format": "date"
    },
    "gender": {
      "$ref": "#/definitions/Gender"
    }
  },
  "required": ["id", "password", "name"]
}

Immediately begin using the com.example.api.Usertype directly in your code, without a compilation step:

User user = User.builder("scott", "mypassword", "Scott")
  .withGender(male)
  .build();

This code uses the builder() method available on all JSON Schema types. Parameters on builder() reflect required types specified in the schema. The withGender() method reflects the gender property, which is not required.

You can make REST calls directly from User:

Requester<User> req = User.request("http://localhost:4567/users");

// Get all Users via HTTP GET
IJsonList<User> users = req.getMany();

// Add a User with HTTP POST
User user = User.builder("scott", "mypassword", "Scott")
  .withGender(male)
  .build();
req.postOne(user);

// Get a User with HTTP GET
String id = user.getId();
user = req.getOne("/$id"); // string interpolation too :)

// Update a User with HTTP PUT
user.setDob(LocalDate.of(1980, 7, 7));
req.putOne("/$id", user);

// Delete a User with HTTP DELETE
req.delete("/$id");

All JSON Schema types have a request() method that takes a URL representing a base location from which REST calls can
be made. As shown request()provides methods to conveniently invoke HTTP GET, POST, PUT, PATCH, and DELETE. You can
also specify authentication, header values, etc. via request().

You can make changes to User.jsonand immediately use the changes in your code. You can refactor/rename, change types, etc. and immediately see the changes take effect in your code. Easily navigate from code usages to the declarations inUser.json. Find your code usages from any declared element in User.json.

Watch this short screencast to see all of this in action. http://manifold.systems/images/json.mp4

Fluent API

JSON types are defined as a set of fluent interface APIs. For example, the UserJSON type is a Java interface and provides type-safe methods to:

  • create a User
  • build User
  • modify properties of a User
  • load User from a string, a file, or a URL using HTTP GET
  • request Web service operations using HTTP GET, POST, PUT, PATCH, & DELETE
  • write User as formatted JSON, YAML, or XML
  • copy User
  • cast to User from any structurally compatible type including Map, all without proxies

Additionally, the API fully supports the JSON Schema format including:

  • Properties marked readOnly or writeOnly
  • Nullable properties
  • additionalProperties and patternProperties
  • Nested types
  • Recursive types
  • External types
  • format types
  • allOf composition types
  • oneOf/anyOf union types

JSON Schema constraints are not enforced in the API, however, because:

  • This is the purpose of a JSON Schema validator
  • Validating constraints can be expensive — it should be optional

Learn more about Manifold’s JSON and JSON Schema support.

SEE ALSO: Manifold: Alien technology

Templatized responses

With Manifold, you can build templates with the full expressive power of Java. Manifold’s advanced, lightweight template engine, ManTL, supports the full Java language, type-safe arguments to templates, type-safe inclusion of other templates, shared layouts and custom base classes for application-specific logic, among other features.

<%@ import java.util.List %>
<%@ import com.example.User %>
<%@ params(List<User> users) %>
<html lang="en">
<body>
<% users.stream()
   .filter(user -> user.getDob() != null)
   .forEach(user -> { %>
    User: ${user.getName()} <br>
    DOB: ${user.getDob()} <br>
<% }); %>
</body>
</html>

Then use the template type-safely in your code:

UsersDob.render(users)

Use the Manifold plugin for IntelliJ IDEA for a refined template authoring experience. Features like deterministic code completion, in-line help, usage searching, highlighting, etc. are all at your fingertips.

What about the API server?

You can use Manifold with SparkJava to build a nice REST service. SparkJava is a micro framework for creating web applications in Java with minimal effort. Here is an HTTP PUT request route to update a User:

// Update existing User
put("/users/:id", (req, res) ->
  UserDao.updateUser(req.params(":id"),
    User.load().fromJson(req.body())).write().toJson());

Clone the source code for the manifold-sample-rest-api project and experiment with Manifold yourself.

Additional features

There are plenty of other features you can use to develop your REST service. For instance, you may have noticed some of the code samples above make use of Manifold’s String Templates feature, also known as string interpolation. You can use the $ character to embed variables and Java expressions directly into any String literal:

int hour = 8;
String time = "It is $hour o'clock";  // prints "It is 8 o'clock"

You can also add your own Extension Methods to any Java type including JRE classes like String and List and even your REST API interfaces:

@Extension
public class MyUserMethods {
  public static Integer getAge(@This User thiz) {
    LocalDate dob = user.getDob();
    return dob == null ? null : 
      Period.between(user.getDob(), LocalDate.now()).getYears();
  }
  
  // more extension methods
  ...
}

Then you can use your methods directly like this:

User user = ...;
int age = user.getAge();

These are just a sampling of the Manifold framework features you can use to refine and improve your project.

To sum up

In this post, I’ve introduced Manifold as a breakthrough Java framework you can use to streamline your REST API development process. With it, you can eliminate code generators from your build script along with the host of problems associated with them. Using the Manifold IntelliJ IDEA plugin you can further improve your development experience: directly navigate to your JSON Schema elements, find usages of them in your code, use deterministic refactor and rename tooling, compile changes incrementally, etc. I also covered some of the JSON API features such as the request() method for direct HTTP functionality. Additionally, I briefly demonstrated how you can incorporate templatized responses, string interpolation, and extension methods into your project. Finally, I demonstrated how you can create a solid REST service using Manifold with SparkJava — a lightweight, powerful combination.

Manifold is a breakthrough technology for the Java ecosystem. It’s also a free, open source project on GitHub. Check it out!

 

This article was originally published on Medium

Author

Scott McKinney

Scott McKinney is the founder and principle engineer at Manifold Systems. Previously, he was a staff engineer at Guidewire where he designed and created Gosu. He currently pounds code by the truckload while listening to way too much retro synthwave.


Leave a Reply

Your email address will not be published.