Work with XML and JSON

This tutorial features services publishing a mix of XML and JSON, and explains how to combine them and expose the composed services as a REST API.

It shows starting an Hazelcast Flow project from scratch so, if you’re unfamiliar with Flow, this is the perfect starting point.

Overview

This guide shows how to connect:

// A service the returns a list of Films (in XML)
operation findAllFilms():FilmList

// A service that returns the cast of a film (in XML)
operation findCast(FilmId):ActorList

// A service that returns the list of awards a film has won (in JSON)
operation findAwards(FilmId):Award[]

The services in our demo

We’ll stitch these together, and expose a REST API that returns the data from all three services.

Set up

To get started working on Flow, follow the instructions at Development Quickstart guide

Describe the Film Service

Write some Taxi that describes our Film service:

  • films.taxi

  • films-data.xml

import flow.formats.Xml

// The @Xml annotation tells Flow how to read this object
@Xml
model FilmList {
  item: Film[]
}

model Film {
  id: FilmId inherits Int
  title: FilmTitle inherits String
  yearReleased: YearReleased inherits Int
}

service FilmsService {
  @HttpOperation(url = "http://localhost:8044/films", method = "GET")
  operation getAllFilms(): FilmList
}
<List>
  <item id="0">
    <title>ACADEMY DINOSAUR</title>
    <yearReleased>2005</yearReleased>
  </item>
  <item id="1">
    <title>ACE GOLDFINGER</title>
    <yearReleased>1975</yearReleased>
  </item>
</List>

Integrate the Cast Service

Our CastService takes the ID of a Film, and returns a list of actors:

  • actors.taxi

  • actor-data.xml

import flow.formats.Xml

@Xml model ActorList {
  item : Actor[]
}

model Actor {
  id : ActorId inherits Int
  name : ActorName inherits String
}

service CastService {
  @HttpOperation(url = "http://localhost:8044/film/{filmId}/cast", method = "GET")
  operation fetchCastForFilm(@PathVariable("filmId") filmId : FilmId):ActorList
}
<List>
  <item>
    <id>34</id>
    <name>JUDY DEAN</name>
  </item>
  <item>
    <id>21</id>
    <name>ELVIS MARX</name>
  </item>
</List>

What connects it together

Flow now has enough information to link our services together:

// The FilmId from our Film model...
model Film {
  id : FilmId inherits Int
  ...
}

// ... is used as an input to our fetchCastForFilm operation:
operation fetchCastForFilm(FilmId):ActorList

We don’t need to write any integration code or resolvers. There’s enough information in the schemas.

We’ve written a bit more Taxi here, as we chose not to work with the service’s XSD directly (e.g., it wasn’t available, or it didn’t exist). If these services published XSDs or WSDLs, we could’ve leveraged them, and only needed to declare the Taxi scalars, such as FilmId.

Write Data Queries

Flow uses type metadata to understand how to link things together. Rather than writing integration code, we write a query for data using TaxiQL.

Fetch the list of films

// Just fetch the ActorList
find { FilmList }

Which returns:

{
   "item": [
      {
         "id": 0,
         "title": "ACADEMY DINOSAUR",
         "yearReleased": 2005
      },
      {
         "id": 1,
         "title": "ACE GOLDFINGER",
         "yearReleased": 1975
      },
      // snip
   ]
}

Restructure the result

We’d like to remove the item wrapper (which is carried over from the XML format), so we change the query, to ask just for a Film[]

find { FilmList } as Film[]

Which returns:

[
  {
   "id": 0,
   "title": "ACADEMY DINOSAUR",
   "yearReleased": 2005
  },
  {
   "id": 1,
   "title": "ACE GOLDFINGER",
   "yearReleased": 1975
  }
]

Define a custom response object

We can define a data contract of the exact data we want back, specifying the field names we like, with the data type indicating where the data is sourced from:

find { FilmList } as (Film[]) -> {
    filmId : FilmId
    nameOfFilm : FilmTitle
} []

To include data from our CastService, we just ask for the actor information:

find { FilmList } as (Film[]) -> {
    filmId : FilmId
    nameOfFilm : FilmTitle
    cast : Actor[]
} []

Which now gives us:

{
   "filmId": 0,
   "nameOfFilm": "ACADEMY DINOSAUR",
   "cast": [
      {
         "id": 18,
         "name": "BOB FAWCETT"
      },
      {
         "id": 28,
         "name": "ALEC WAYNE"
      },
    //..snip
   ]
}

Add our Awards Service

We can also define a schema and service for our awards information, which is returned in JSON:

  • awards.taxi

  • awards-data.json

model Award {
  title: AwardTitle inherits String
  yearWon: YearWon inherits Int
}

service AwardsService {
  @HttpOperation(url = "http://localhost:8044/film/{filmId}/awards", method = "GET")
  operation fetchAwardsForFilm(@PathVariable("filmId") filmId: FilmId): Award[]
}
[
  {
    "title": "Best Makeup and Hairstyling",
    "yearWon": 2020
  },
  {
    "title": "Best Original Score",
    "yearWon": 2020
  },
  // snip\...
]

Enrich our query

Finally, to include this awards data, we just add it to our query:

find { FilmList } as (Film[]) -> {
  filmId: FilmId
  nameOfFilm: FilmTitle
  cast: Actor[]
  awards: Award[]
} []

Which gives us:

{
   "filmId": 0,
   "nameOfFilm": "ACADEMY DINOSAUR",
   "cast" : [] // omitted
   "awards": [
      {
         "title": "Best Documentary Feature",
         "yearWon": 2020
      },
      {
         "title": "Best Supporting Actress",
         "yearWon": 2020
      },
   ]
}

Publish our query as a REST API

Now that we’re happy with our response data, we can publish this query as a REST API.

  • First, we wrap the query in a query { ... } block, and save it in our Taxi project

  • Then we add an @HttpOperation(...) annotation

  • query.taxi

@HttpOperation(url = '/api/q/filmsAndAwards', method = 'GET')
 query filmsAndAwards {
      find { FilmList } as (Film[]) -> {
          filmId : FilmId
          nameOfFilm : FilmTitle
          awards : Award[]
          cast : Actor[]
      } []
 }

Our query is now available at http://localhost:9021/api/q/filmsAndAwards

$ curl http://localhost:9021/api/q/filmsAndAwards | jq

Which gives us:

[
  {
    "filmId": 0,
    "nameOfFilm": "ACADEMY DINOSAUR",
    "awards": [
      {
        "title": "Best Animated Feature",
        "yearWon": 2020
      },
      {
        "title": "Best Original Score for a Comedy",
        "yearWon": 2020
      },
      {
        "title": "Best Documentary Feature",
        "yearWon": 2020
      },
      // .... snip
    ]
  }
]

Publish a query using the UI

To publish a query as an endpoint using the UI:

  • Choose Query editor and in the editor, write your query

  • Click Run to make sure the query runs with no errors

  • Click the Save query to project button, choose a project (this must be editable), give your query a name and then save it

  • Click the Publish endpoint button and publish it as an HTTP or WebSocket endpoint, depending on the query

  • Choose Endpoints and make sure the query is running (you can disable/enable the endpoint if necessary)

Wrap up and next steps

In this guide, we’ve:

  • Created a new project

  • Exposed XML services and modelled their responses

  • Written a query stitching three services together

  • Published that query as an HTTP service