Visual REST API Design with Sparx Enterprise Architect

Michael Bica, Ph. D.
7 min readApr 4, 2022

--

Introduction

As a software architect, the main technique I use to express my ideas is visual modelling. If you have been in the industry long enough, you may remember names like James Rumbaugh, Grady Booch and Ivar Jacobson, which were the pioneers of software modelling techniques, the founders of Rational Software Corporation, and the creators of the Unified Modelling Language (UML). UML compliant software design tools, such as Rational Rose, MagicDraw and Sparx Enterprise Architect have been available in the industry from the mid ‘90s.

In 2000 IEEE has published the IEEE 1471–2000 standard (now the ISO 42010) that drastically changed my notion of what a software architecture is meant to be.

You wonder, why this trip down memory lane? Why does it matter?

Motivation

In order to share an architecture vision, an architect uses different architecture views that address specific stakeholder concerns. These visual representations can be high-level conceptual constructs when trade-offs are considered, or detailed, high-fidelity engineering blueprints used to drive the development work. Describing a solution or system architecture in words is quasi impossible, as hard as describing a musical composition without using musical notes as the communication medium.

Specific advantages of visual modelling using UML are efficiency to produce the design, ease of understanding and communicating ideas with stakeholders and increased semantic consistency

Since the microservice architecture style has been developed, interfaces are no longer coding artifacts, but products, as teams or companies developing them are not the same teams or companies consuming them. Being able to have a high-fidelity description of the REST API designs and being able to comprehend what they offer without reading thousands of OpenAPI lines of YAML files has become a need both on the producer and consumer sides of the REST API ecosystem.

There are very good API publishing and management tools available (e.g. Swagger, Apigee, Stoplight) , yet, so far, I am aware only of a product that supports visual design of REST APIs — Visual Paradigm. As I use Sparx EA for all architecture documentation tasks, I am interested to investigate if it can be effectively used to model REST APIs.

Can REST APIs be Visually Modelled?

The OpenAPI Specification has become the de-facto standard for REST API definitions. Two key objects of the specification are paths and components. REST API design is domain driven, being focused on Resources. As such resources represent a domain model, which can be represented in UML using Classes visualized in Class Diagrams. UML was focused on object oriented design, thus a UML Class has both data elements and behavioural elements. In the REST API world, the methods are anchored to the the standard HTTP request methods (modelled as paths). Resources have only data elements.

As the OpenAPI specification is an Interface Definition Language (IDL), paths can be modelled as Interfaces with stereotyped methods. A path may have only one of the HTTP standard methods, the ones most commonly used in REST APIs being GET, POST, PUT, PATCH, and DELETE.

Let’s Do It

Let’s design a REST API for an Accounts Domain, having straightforward requirements: one can get a filtered list of accounts, view accounts details, create a new account, update account information (e.g. account name) and inactivate an account. Accounts may be of several types, for example cash or savings account. A cash account has a monthly fee, while a savings account has an annual interest rate as distinct data elements. For each account, an advisor may add notes. Accounts may have one or several owners, which belong to the Party domain. One Party may have several accounts. A client of the REST API also needs to be able to retrieve basic information about the Parties that own an account.

REST API is Resource centric, thus we start with designing the Resources. The visual representation of the Account Domain Resources is shown in the diagram below:

Notes:

  1. Classes are stereotyped as <<Resource>> to convey that there are no operations
  2. The OpenAPI Specification can be used to model inheritance; in order to be semantically correct, the parent component must have a discriminator, which is the ‘account_type’ in this example
  3. The UML aggregation notation is used to model referenced components
  4. To show a list of accounts, an Account Summary resource is used
  5. Tags are used to specify required fields and field level constraints

The equivalent YAML file is:

components:
schemas:
Account:
required:
- 'name'
- 'account_type'
type: object
properties:
id:
type: integer
format: int32
nullable: false
readOnly: true
name:
type: string
owners:
type: array
items:
$ref: '#/components/schemas/Owner'
notes:
type: array
items:
$ref: '#/components/schemas/Note'
account_type:
$ref: '#/components/schemas/AccountType'
balance:
$ref: '#/components/schemas/Money'
discriminator:
propertyName: account_type
AccountSummary:
type: object
properties:
account_id:
type: integer
format: int32
account_name:
type: string
account_type:
type: integer
format: int32
balance:
$ref: '#/components/schemas/Money'
AccountType:
type: string
enum:
- CASH
- SAVINGS
Accounts:
type: object
properties:
accounts:
type: array
items:
$ref: '#/components/schemas/AccountSummary'
CashAccount:
allOf:
- $ref: '#/components/schemas/Account'
- type: object
properties:
monthly_fee:
type: number
format: double
Currency:
type: string
enum:
- USD
- CAD
- EUR
Money:
required:
- 'amount'
- 'currency'
type: object
properties:
amount:
type: number
format: float
currency:
$ref: '#/components/schemas/Currency'
Note:
required:
- 'subject'
- 'body'
type: object
properties:
id:
type: integer
format: int32
nullable: false
readOnly: true
action:
type: string
maxLength: 1
subject:
type: string
maxLength: 3
body:
type: string
complete_date:
type: string
format: date
follow-up_date:
type: string
format: date
Owner:
type: object
properties:
id:
type: integer
format: int32
nullable: false
readOnly: true
first_name:
type: string
last_name:
type: string
primary_email:
type: string
primary_phone:
type: string
SavingsAccount:
allOf:
- $ref: '#/components/schemas/Account'
- type: object
properties:
annual_interest:
type: number
format: double
Error:
type: object
properties:
id:
type: string
type:
type: string
title:
type: string
detail:
type: string
instance:
type: string
invalidParameters:
type: array
items:
type: object
properties:
parameters:
type: array
items:
type: string
reason:
type: string

After modelling the resources, the next step is to model the paths. This is a simple task; paths are flat and use standard HTTP methods. As we want to have both a search method that returns a list of accounts, and to be able to retrieve data of an individual account, two objects will be used, one called “Accounts” and one called “Account”. The path diagram is below

Paths Model

There is nothing really remarkable here, but it’s worth explaining the notations in the diagram:

  • Methods are stereotyped with the HTTP verbs
  • Methods are using the resources defined above
  • The paths are specified as tags
  • By default Sparx EA shows the types of the method parameters, but it can be configured to show the parameter names
  • For the search method that returns a list of accounts, neither the pagination (offset and limit) nor the sort query parameters are visible in the model. These parameters can be modelled using tags at the method level

This diagram maps to the following OpenAPI specification fragment:

paths:
/accounts/{accountId}:
get:
tags:
- Accounts
operationId: account.getAccount
parameters:
- name: Accept-Language
in: header
schema:
type: string
- name: accountId
in: path
schema:
type: string
required: true
responses:
200:
description: Search results
content:
application/json:
schema:
$ref: '#/components/schemas/Account'
400:
description: Bad Request
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Error'
401:
description: Unauthorized
500:
description: Internal Server Error
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Error'
delete:
tags:
- Accounts
operationId: account.deleteAccount
parameters:
- name: Accept-Language
in: header
schema:
type: string
- name: accountId
in: path
schema:
type: string
required: true
responses:
200:
description: Delete successful
400:
description: Bad Request
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Error'
401:
description: Unauthorized
500:
description: Internal Server Error
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Error'
patch:
tags:
- Accounts
operationId: account.updateBalance
parameters:
- name: Accept-Language
in: header
schema:
type: string
- name: accountId
in: path
schema:
type: string
required: true
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Money'
responses:
200:
description: Update successful
400:
description: Bad Request
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Error'
401:
description: Unauthorized
500:
description: Internal Server Error
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Error'
/accounts:
get:
tags:
- Accounts
operationId: accounts.searchAccounts
parameters:
- name: Accept-Language
in: header
schema:
type: string
- name: account_id
in: query
schema:
type: integer
format: int32
- name: account_type
in: query
schema:
type: array
items:
type: string
style: form
explode: false
- name: phone_number
in: query
schema:
type: string
style: form
explode: false
- name: sort
in: query
schema:
type: string
style: form
explode: false
- name: offset
in: query
schema:
type: integer
format: int32
- name: limit
in: query
schema:
type: integer
format: int32
responses:
200:
description: Search results
content:
application/json:
schema:
$ref: '#/components/schemas/Accounts'
400:
description: Bad Request
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Error'
401:
description: Unauthorized
500:
description: Internal Server Error
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Error'
post:
tags:
- Accounts
operationId: accounts.createAccount
parameters:
- name: Accept-Language
in: header
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Account'
responses:
201:
description: Create successful
400:
description: Bad Request
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Error'
401:
description: Unauthorized
500:
description: Internal Server Error
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Error'
put:
tags:
- Accounts
operationId: accounts.updateAccount
parameters:
- name: Accept-Language
in: header
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Account'
responses:
200:
description: Update successful
400:
description: Bad Request
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Error'
401:
description: Unauthorized
500:
description: Internal Server Error
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Error'

What Else Can Be Done?

Modelling REST APIs in Sparx EA is extremely easy. Advanced users may consider developing a MDG technology to customize the toolbars specifically for resource and path modelling.

Check out my follow up post, that shows how to generate the OpenAPI Specification from the Sparx model. To show how easy it is, I’m including a Python script that can be used ‘as is’, or extended to accommodate for additional use cases.

When these two additional steps are implemented, one can expect not only improved efficiency when designing the REST APIs, but also improved quality of the OpenAPI specification.

I will cover these two extra steps in my next story.

--

--

Michael Bica, Ph. D.

Software architect, interested in data science and evolutionary algorithms, exploring how to enhance the quality of data driven decisions in our daily lives..