§12.5.

Service-oriented architecture

Service-oriented architecture (SOA) divides the domain logic of application into largely independent components that run on separate servers.

SOA is an extension of the multitier architecture. In a service-oriented architecture, the domain logic still resides in a tier. However, we deploy different functions of the domain logic on separate servers within the tier.

SOA is related to other terms used in distributed systems:

RPC and RMI

Remote procedure calls (RPC) and remote method invocation (RMI) are approaches to invoking functions that are running on separate computers.

Microservices

A microservices architecture arises when an organization emphasizes many simple (or ‘micro’) services in a service-oriented architecture.

Tip
Architectural concepts have many interpretations. Many organizations will claim to use a service-oriented architecture. However, the precise implementation of the idea will vary from organization to organization.

Problem

A single ‘monolithic’ domain-logic tier is suitable for most web systems. When the number of incoming requests is too much for a single domain-logic server, the next step is to deploy multiple identical copies of the domain-logic on separate servers. In other words, identical servers running the domain logic share the incoming requests.

However, there comes the point in some very popular websites, when a monolithic domain-logic is too difficult to handle:

  1. It is difficult to change small parts of the application when it is deployed as a complete unit.

  2. Developers need to work more closely together if there is only a single code-base. One code-base may become impractical if there are hundreds or thousands of developers.

  3. A severe bug may impact the availability of the entire tier, rather than just a specific feature.

  4. Developers may need to standardize on one framework instead of choosing the best tools to solve each problem.

  5. The development of prototypes or experimental features may be slowed down by a need to comply with the quality standards expected in the ‘main functionality’.

  6. The domain logic may be so large and complex that it takes too long for developers to compile, run or test on their computer.

  7. Administrators cannot deploy separate features to specialized hardware (e.g., deploying CPU-intensive processing to servers with powerful CPUs versus deploying RAM or disk-intensive operations to servers with better disk bandwidth)

Solution

A service-oriented architecture splits the application into sub-components (‘services’). Instead of a single application, each component has the following attributes:

  • Independently deployed and autonomous: services can be deployed and run independently of other services

  • Standardized service contracts: services have well-defined interfaces or service contracts so they can be relied upon by developers building other services

  • Reusable and composable: services can be reused and combined with other services to create more powerful services

  • Encapsulated: services manage their internal data and data schema

From a design perspective, the following steps outline one approach of applying a service-oriented architecture:

  1. Identify independent units of functionality (e.g., user authentication, shopping list tracking, user notification)

  2. Separate the units of functionality into independent components

  3. Establish interfaces and networking infrastructure to deploy those components as independent services

  4. Deploy the services to separate servers

A single page application might communicate directly with individual services. However, more commonly, a simple ‘consumer layer’ or ‘business process layer’ provides a single entry point for all requests from the web application. The consumer layer handles each request by communicating with appropriate individual services (e.g., adding to a shopping list might involve checking the user with an authentication service, adding the item with the shopping list service and then sending messages to other users by the notification service).

Presentation layer

Business process layer

Service layer
(User authentication)

Service layer
(Shopping list)

Service layer
(User notification services)

Persistence layer
(User database)

Persistence layer
(Shopping list database)

Persistence layer
(User notification database)

Tip
In a service-oriented architecture, each service typically uses a private database schema and sometimes even a private database management system. Deploying a service in a service-oriented architecture may, therefore, involve deploying additional database servers.

Of course, these services need a way to communicate with each other. I have already discussed the fetch API. The fetch API, sending JSON requests by HTTP, is one way to implement communication in a service-oriented architecture. More sophisticated service-oriented architectures might use remote procedure call (RPC) or remote method invocation (RMI) [1] technologies. Internally, RPC and RMI operate just like using fetch to send JSON. However, RPC and RMI typically provide a programming experience that simulates local method calls. For example, instead of calling await fetch('https://login-server/api/check-user') an RPC technology might provide developers with a simple function-based interface that is indistinguishable from ordinary JavaScript: login.checkUser(username, password).

Microservices

The term ‘service-oriented architectures’ grew in developer awareness around 2001. [2] Since 2015, ‘microservices’ has eclipsed SOA. [3] Microservices is the application of the same principle as service-oriented architecture. Microservices are typically associated with a much greater emphasis on creating very-small, single-purpose and self-contained services. A microservice typically has a simple interface and is completely self-contained (i.e., it uses a private database instance).

Tip
Before service-oriented architectures were popular, large organizations used a single organization-wide database schema for systems integration. Service-oriented architectures resulted in a shift from thinking about underlying data to behaviors and capabilities. The shift to SOA and microservices avoids the complexity of trying to build a universal database schema that can ‘do everything’: applications can evolve their data models separately.

Implementation

In the team shopping list code of Chapter 10, the expense, icon and notification services are ideal candidates for translation into independent services.

For example, the Chapter 10 code for the notification service is as follows:

// Notify all team members that a new item has been added to the shopping list
function notifyNewItem(description) {
    console.log(`---------------------------------------------`);
    console.log(`To: Everyone`);
    console.log(`From: shopping list app`);
    console.log(`Please buy this item: ${description}`);
}

// Notify all team members that an item has been deleted from the shopping list
function notifyDeletedItem(description) {
    console.log(`---------------------------------------------`);
    console.log(`To: Everyone`);
    console.log(`From: shopping list app`);
    console.log(`Warning! Please don't buy this item: ${description}`);
}

module.exports = { notifyNewItem, notifyDeletedItem };

You can convert the notification functions to fully-fledged independent services using any web framework. Express is an obvious choice:

const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const port = 3002;

app.use(bodyParser.json());

// Notify all team members that a new item has been added to the shopping list
app.post('/notify/create', (req, res) => {
    let description = req.body.description;

    console.log(`---------------------------------------------`);
    console.log(`To: Everyone`);
    console.log(`From: shopping list app`);
    console.log(`Please buy this item: ${description}`);

    res.json({success: true});
});

// Notify all team members that an item has been deleted from the shopping list
app.post('/notify/delete', (req, res) => {
    let description = req.body.description;

    console.log(`---------------------------------------------`);
    console.log(`To: Everyone`);
    console.log(`From: shopping list app`);
    console.log(`Warning! Please don't buy this item: ${description}`);

    res.json({success: true});
});

// Start the server immediately
app.listen(port, () => console.log(`The notification service is running on http://localhost:${port}/`));

In the example above, I have converted each of the functions into endpoints on a service.

The business process layer can make use of this service using a HTTP fetch (or any HTTP client). For example:

async function notifyNewItem(payload) {
    let result = await fetch(
        'http://notificationservice:3001/notify/create',
        {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(payload);
        }
    );
    return await res.json();
}

The business process layer uses this client. For example, the business process layer might make use of the external services createItem, notifyNewItem and getItems:

route.post('/items/new', async (req, res) => {
    let { description, quantity } = req.body;

    // Create and add a new item
    await createItem({description, quantity});
    // Notify everyone on the team
    await notifyNewItem({description})

    let items = await getItems();

    res.json({ success: true, items });
});
Warning
This example code does not include any error handling logic.

The chapter12_soa project is an example of a simple service-oriented architecture. The project is based on chapter10_teamshoppinglist_react but does not include the icon or expense service, nor does it support any actions on created items.

In addition to invoking services using JSON-based HTTP fetch, a range of more structured frameworks or tools can provide a more sophisticated remote procedure call (RPC) or remote method invocation interface for service integration across different platforms and programming languages. Some examples are listed below:

  • gRPC is a simple and efficient RPC framework used internally at Google

  • JSON-RPC and XML-RPC are JSON and XML standards (respectively) for defining and invoking remote functions.

  • SOAP, despite its name (Simple Object Access Protocol), is a complex collection of XML standards for remote procedure calls. While not a popular approach today, many historical service-oriented architectures make extensive use of SOAP. In Node.js, the soap package speaks the SOAP protocol.

  • Moleculer is a Node.js framework specifically designed for SOA/microservices development.

Reflection: Benefits and weaknesses

What might be some disadvantages of a service-oriented architecture or microservices architecture?

In what situations would a RESTful SOA be preferred to a SOA built using gRPC or XMLRPC? What about the converse: when would gRPC be preferred to a RESTful SOA?


1. RPC and RMI are synonymous in most situations. RPC is an older term referring to procedural languages and is usually associated with services that use primitive data structures. RMI refers to object-oriented programming languages, where services return primitive data structures in addition to sophisticated objects that can intelligently proxy subsequent requests back to the original server.
3. See the comparison between microservices and SOA in the Google Search Trends Data.