Build a Microservice in 4 Steps: An Introduction to Jolie
A brief introduction to how the microservice-oriented Jolie programming language can be used to build microservices. We cover the four cardinal elements of service programming: APIs, Services, Access Points, and Behaviour.
Join the DZone community and get the full member experience.
Join For FreeJolie is a microservice-oriented language: it provides linguistic primitives (or abstractions) that address the common concerns of microservice design and implementation, in order to make their development more effective.
In this tutorial, we are going to get familiar with the basic aspects of the language, building a simple service that offers an operation for greeting people over HTTP+JSON. (The version of Jolie used in this article is 1.10.)
You can see a live coding demo in this video (it lasts about 3 minutes):
Step 1: APIs
Let's start from the API (Application Programming Interface) of our service.
First, we define a data type for the requests for greetings that we are going to get from clients, called GreetingRequest
. It contains a string called name
. (Jolie is not highlighted correctly on DZone yet, so we use TypeScript highlighting in this article.)
x
type GreetingRequest { name:string } // The type of greeting requests
Then, we define a data type for the greetings that we are going to send back to clients in response, called Greeting
. It contains a string called greeting
.
x
type Greeting { greeting:string } // The type of greetings
We can now use our data types to define an interface (Jolie for API), which specifies an operation, greet
, that our service is going to offer to clients.
x
interface GreeterIface {
RequestResponse: greet(GreetingRequest)(Greeting)
}
Above, RequestResponse
means that operation greet
receives a request and always sends back a response to clients (Jolie also offers OneWay as an alternative, for simple communication/notifications). The greet
operation expects requests of type GreetingRequest
and sends back responses of type Greeting
.
That's it for the API!
Step 2: Services
Next up, we define our service. Jolie provides a native primitive for defining a service, called (unsurprisingly) service
. We call our service Greeter
. We use the execution
keyword to specify that we want Greeter
to handle clients concurrently.
x
service Greeter {
execution: concurrent
/* More will be added here... */
}
The definition of service Greeter
needs to be filled with two essential components: at least one access point, which defines how the service can be accessed by clients, and its behavior, which defines the business logic that actually implements the API offered by the service. We write these components in the following (and last) two steps.
Step 3: Access Points
An access point is created by using the keyword inputPort
. To define an access point, we must state:
- The location at which clients can reach the service.
- The transport protocol that clients should use to communicate.
- The interfaces (that is, the APIs) that can be accessed.
Each one of these components has a corresponding primitive in Jolie. Below, we define an access point that:
- Accepts connections on TCP port 8080.
- Uses HTTP as a transport protocol, encoding data with the JSON format by default.
- Exposes the GreeterIface API.
x
service Greeter {
execution: concurrent
inputPort GreeterInput {
location: "socket://localhost:8080"
protocol: http { format = "json" }
interfaces: GreeterIface
}
/* More will be added here... */
}
Step 4: Behavior
We now define a behavior for our Greeter
service. We want it to:
- Receive a message for the
greet
operation from any client. In Jolie, this is done simply by writing the name of the operation. - Store the received client request in a variable, say
request
. - Compute a response containing a greeting, for example using a variable called
response
.
This corresponds to the following Jolie code.
xxxxxxxxxx
service Greeter {
execution: concurrent
inputPort GreeterInput {
location: "socket://localhost:8080"
protocol: http { format = "json" }
interfaces: GreeterIface
}
main {
greet(request)(response) {
response.greeting = "Hello, " + request.name + "!"
}
}
}
Running Your Service
Let's review the code that we have so far:
xxxxxxxxxx
type GreetingRequest { name:string } // The type of greeting requests
type Greeting { greeting:string } // The type of greetings
interface GreeterIface {
RequestResponse: greet(GreetingRequest)(Greeting)
}
service Greeter {
execution: concurrent
inputPort GreeterInput {
location: "socket://localhost:8080"
protocol: http { format = "json" }
interfaces: GreeterIface
}
main {
greet(request)(response) {
response.greeting = "Hello, " + request.name + "!"
}
}
}
Let's store this code in a file with the .ol suffix, say main.ol, and we are ready to go. Run this command:
xxxxxxxxxx
jolie main.ol
Your service is now waiting for client requests. Let's give it one! Run the following curl command:
xxxxxxxxxx
curl http://localhost:8080/greet?name=Jolie
You'll see the output:
xxxxxxxxxx
{"greeting":"Hello, Jolie!"}
Want More?
Visit our website for installing Jolie, browse its official documentation, and see how to containerize Jolie services.
If you want to get in touch, we are active on our Discord server!
Bonus: A Reflection on Effectiveness and Technology Agnosticism
This simple example is already enough to showcase a couple of important aspects of Jolie.
The first aspect is that the coding of a service in Jolie is very similar to the usual conceptual models used for services: we have defined data types (for the data model), an interface, a service, an access point, and an implementation (the behavior). This helps in making us productive!
The second aspect is that Jolie is designed for integration. If you like, Jolie is a technology designed for technology agnosticism. Its data type language captures DTOs (Data Transfer Objects), assuming only native types that are available in most technologies (strings, booleans, etc.). The language for expressing behaviors abstracts from how data is encoded on the wire: for example, in the behavior that we wrote above, nothing says that the (data in the) variables request
and response
are going to be de-/encoded in JSON. This is visible only in the access point, which we are thus free to change as we see fit. For example, say that we wanted to access our Greeter
service by sending requests encoded in XML instead of JSON. We just need to change the input port of Greeter
to use XML as follows:
xxxxxxxxxx
service Greeter {
execution: concurrent
inputPort GreeterInput {
location: "socket://localhost:8080"
protocol: http { format = "xml" } // < This is the only change
interfaces: GreeterIface
}
main {
greet(request)(response) {
response.greeting = "Hello, " + request.name + "!"
}
}
}
Similarly, we could make Greeter
accessible over binary protocols or even different kinds of protocols at once.
These are just a few tastes of some of the design principles of Jolie. We'll explore more and delve deeper in the future!
Parts of this article previously appeared on https://fmontesi.github.io/2015/02/06/programming-microservices-with-jolie.html and https://fmontesi.github.io/2020/09/30/service-oriented-programming-languages.html.
Opinions expressed by DZone contributors are their own.
Comments