DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Next.js vs. Express.js: What Are the Differences?
  • The Missing Link in Cybersecurity: Culture
  • 3 Easy Steps for a (Dev)Containerized Microservice With Jolie and Docker
  • Technology for People: How to Develop an Engineering Culture and Make a Quantum Leap In Development

Trending

  • How to Convert XLS to XLSX in Java
  • Integrating Security as Code: A Necessity for DevSecOps
  • A Complete Guide to Modern AI Developer Tools
  • Mastering Advanced Traffic Management in Multi-Cloud Kubernetes: Scaling With Multiple Istio Ingress Gateways
  1. DZone
  2. Coding
  3. JavaScript
  4. Comparing Express With Jolie: Creating a REST Service

Comparing Express With Jolie: Creating a REST Service

How Express and Jolie look like in the development of a simple REST service

By 
Fabrizio Montesi user avatar
Fabrizio Montesi
·
Balint Maschio user avatar
Balint Maschio
·
Nov. 02, 22 · Analysis
Likes (4)
Comment
Save
Tweet
Share
4.8K Views

Join the DZone community and get the full member experience.

Join For Free

Jolie is a service-oriented programming language, developed with the aim of making some key principles of service-oriented programming syntactically manifest. Express is a leading framework in the world of JavaScript for the creation of REST services.

The concepts behind Express/JavaScript and Jolie are not that distant, but they are offered under different shapes: one aimed at building on top of JavaScript and Node.js, the other aimed at expressing these concepts declaratively. The aim of this article is to identify the first differences that arise between Express and Jolie in the development of a REST service by comparing how the same concepts are codified.

We ground our comparison in a concrete example: a simple REST service that exposes an API for retrieving information about users. Users are identified by username and associated to data that includes name, e-mail address, and an integer representing "karma" that the user has in the system. In particular, two operations are possible:

  • Getting information about a specific user (name, e-mail, and karma counter) by passing its username, for example by requesting /api/users/jane.
  • Listing the usernames of the users in the system, with the possibility of filtering them by karma. For example, to get the list of usernames associated to minimum karma 5, we could request /api/users?minKarma=5.

We assume some familiarity with JavaScript.

Express

The following code implements our simple example with Express.

JavaScript
 
const express = require('express')
const http = require('http')
const { validationResult , query } = require('express-validator')

const app = express()

const users = {
	jane: { name: "Jane Doe", email: "jane@doe.com", karma: 6 },
	john: { name: "John Doe", email: "john@doe.com", karma: 4 }
}

app.get('/api/users/:username', (req, res) => {
	const errors = validationResult(req);
	if (!errors.isEmpty()) {
		return res.status(400).json({ errors: errors.array() });
	}

	if (users[req.params.username]) {
		return res.status(200).json(users[req.params.username])
	} else {
		return res.status(404).json(req.params.username)
	}
})

app.get('/api/users', query('minKarma').isNumeric(), (req, res) => {
	const errors = validationResult(req);
	if (!errors.isEmpty()) {
		return res.status(400).json({ errors: errors.array() });
	}

	let usernames = []
	for (username in users) {   
		if (req.query.minKarma && req.query.minKarma < users[username].karma) {
			usernames.push(username)
		}
	}
  
	const responseBody = {}
	if (usernames.length != 0) {
		responseBody['usernames'] = usernames
	}
	res.status(200).json(responseBody)
})

app.listen(8080)


We start by importing from some library modules, using require, and then we create the application (const app = express()).

To represent data about users, we use a simple object (users) that associates usernames (as properties) to objects with name, e-mail address, and karma. We have two users, respectively identified by the usernames john and jane.

We then add two routes to our application:

  1. The first route is for getting information about a specific user. We start by validating the request and returning immediately in case there are any validation errors. Then, we check if the requested username is in our users data object: if so, we return information about that user; otherwise, we return an error with status code 404.
  2. The second route is for listing users. The boilerplate for validation and error checking is similar. We use a for loop to find the usernames of users with sufficient karma and accumulate them in an array, which is then returned to the caller.

After we are done configuring our application, we launch it by listening on TCP port 8080.

Jolie

We now implement the same example in Jolie.

 
type User { name: string, email: string, karma: int }
type ListUsersRequest { minKarma?: int }
type ListUsersResponse { usernames*: string }
type ViewUserRequest { username: string }

interface UsersInterface {
RequestResponse:
	viewUser( ViewUserRequest )( User ) throws UserNotFound( string ),
	listUsers( ListUsersRequest )( ListUsersResponse )
}

service App {
	execution: concurrent

	inputPort Web {
		location: "socket://localhost:8080"
		protocol: http {
			format = "json"
			osc << {
				listUsers << {
					template = "/api/user"
					method = "get"
				}
				viewUser << {
					template = "/api/user/{username}"
					method = "get"
					statusCodes.UserNotFound = 404
				}
			}
		}
		interfaces: UsersInterface
	}

	init {
		users << {
			john << {
				name = "John Doe", email = "john@doe.com", karma = 4
			}
			jane << {
				name = "Jane Doe", email = "jane@doe.com", karma = 6
			}
		}
	}
    
	main {
		[ viewUser( request )( user ) {
			if( is_defined( users.(request.username) ) ) {
				user << users.(request.username)
			} else {
				throw( UserNotFound, request.username )
			}
		} ]

		[ listUsers( request )( response ) {
			i = 0
			foreach( username : users ) {
				user << users.(username)
				if( !( is_defined( request.minKarma ) && user.karma < request.minKarma ) ) {
					response.usernames[i++] = username
				}
			}
		} ]
	}
}


We start by declaring the API (types and interface) of the service, with the two operations for listing and viewing users. Then, we define the implementation of our service by using a service block, which is configured to handle client requests concurrently (execution: concurrent).

The input port Web defines an HTTP access point for clients, and contains a protocol configuration that maps our two operations to the expected HTTP resource paths (using URI templates), method, and status codes.

The data structure about users is defined in the block for service initialisation (init). The << operator is used in Jolie to make deep copies of data trees.

The behaviour of the service is defined by the main procedure, which offers a choice ([..]{..} [..] {..}) between our two operations.

The operations are implemented using a logic similar to that seen in the previous JavaScript code snippet. In viewUser, if the requested username cannot be found, we throw a fault (UserNotFound)—users.(request.username) in Jolie roughly translates to users[request.username] in JavaScript.

Comparison

From this simple example, we can observe a few interesting differences. We focus on concepts, leaving minor details or aestethic differences out of the discussion.

API-First and Validation

The Jolie code starts by defining explicitly the API of the service, whereas in Express the API is implicitly given by the routes created on the application object. (Message types can actually be omitted in Jolie, but it is considered a best practice to include them.)

We might say that Jolie follows an "API-first" approach, whereas in Express APIs can be defined after a service is coded (or not at all, even).

There are two important features that Jolie provides out-of-the-box if you provide message types.

  • Runtime Checks: all messages are checked at runtime to respect their expected types. So writing code for validating the request is not necessary in Jolie.
  • Automatic Casting: parameters in querystrings are automatically converted from strings to their expected types. If casting is not possible, an error is returned to the client automatically. In Express, req.query.minKarma is a string, which JavaScript can cast automatically to a number when needed. To check that this would not be a problem, we had to add the validation code query('minKarma').isNumeric().

Manual versions of these features can be implemented in an Express application. For example, one can use the express-validator middleware in the configuration of the application in order to check that messages respect some JSON schemas. Jolie offers additional integrated mechanisms for performing validation, for example refinement types and service decorators. We leave a comparison of validation techniques in Express and Jolie to another article.

Code Structure

Defining a service requires dealing with its access points (how it can be accessed by clients) and its implementation (see also the 5 principles of service-oriented programming).

In Express, these aspects are coded by calling methods of the application object. The approach is multi-paradigmatic: it combines objects, functions, and imperative programming. The service is represented as an object (app in our example), and the operations offered by the service are defined together with their implementations step-by-step. The definition of a service is given by the internal state of the application object (modified by invoking the get method in our example).

In Jolie, the configuration of access points and the business logic of the operations that the service offers are kept separate. The definition of a service and its access points (service App and inputPort Web in our example, respectively) are given in a declarative approach, which makes the structure of a service syntactically manifest. Jolie is service-oriented: the necessary concepts for defining a service are supported by linguistic primitives (in contrast to libraries).

The implementation of each operation in Jolie is then given in the main block, and is pure business logic that abstracts from the concrete data format (and even transport) used by the access point. The mapping from resource paths to operations (and faults to status codes) is given in the configuration of the access point. In general, operation implementations can be reused with different access point configurations. For example, we could easily reconfigure our service to send XML responses by changing format = "json" in the configuration of the input port to format = "xml".

Conclusion

In this article, we have explored the differences between Express and Jolie in the context of a simple REST service. True to the dynamic nature of JavaScript, Express sets up the API and access point of a service by using subsequent method invocations, whereas in Jolie this is achieved with a more static and declarative approach. Another interesting difference lies in the approaches to business logic: the syntax of Jolie naturally guides the developer towards keeping business logic abstract from concrete data formats.

Express Jolie (programming language)

Opinions expressed by DZone contributors are their own.

Related

  • Next.js vs. Express.js: What Are the Differences?
  • The Missing Link in Cybersecurity: Culture
  • 3 Easy Steps for a (Dev)Containerized Microservice With Jolie and Docker
  • Technology for People: How to Develop an Engineering Culture and Make a Quantum Leap In Development

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!