Vadim Popov

@Vadim.Popov

Using Tarantool in a .NET project on Windows

March 13th 2017

Original article available at https://habrahabr.ru/company/mailru/blog/321998/

There has lately been quite a few articles circulating on the Internet about Tarantool, an all-in-one database and application server used by Mail.Ru Group, Avito and Yota in their high-load projects. So when a small startup that I’m an independent consultant for faced the necessity of splitting their great but, unfortunately, monolithic application into microservices, I figured it would be worthwhile to take a closer look at Tarantool. However, unlike most companies relying on Tarantool, that startup used Visual Studio to develop its project on Windows. Even after adopting the microservice architecture most microservices would be written in C#. As for Tarantool…when you open its official site, it immediately becomes apparent you’ll have hard time installing it on Windows, since it’s not ported to this operating system. In this article, I’m going to describe how I overcame these difficulties, what microservice I picked Tarantool for and how you can use Tarantool in your .NET projects. Jumping ahead, let me tell you this: almost any issue can be solved, and I know this first-hand. For example, it took me less than ten minutes to install and run Tarantool and then query it from my C# code — and I’ll show you how to do it!

This article can be viewed as a tutorial detailing how to work with Tarantool. You won’t find here the description of the project built with Tarantool or the results of benchmarking Tarantool against the competition (you can find plenty of those online). This tutorial was created as an attempt to answer the following question: what would I have liked to read when I was just getting started with Tarantool? But let me first dwell a little on what Tarantool’s used for in real life.

Using Tarantool

One microservice that’s crucial for almost any project (and creating which preoccupied me the most) is that of authentication and authorization. First and foremost, it was necessary to define the requirements for a database that would hold information about users. These were few, but only a small fraction of products met all of them. Besides, the fewer products the better, as it simplifies configuration and maintenance. That’s why the MySQL + Redis + RabbitMQ bundle would definitely be an overkill. Below are our basic requirements:

  • Information about users data must be safely stored on disk.
  • The database must support multi-node data replication. With one virtual machine, can there really be any decent uptime or reliability? We have to admit we’ve been living in the world of distributed systems for quite some time, and having a single server running MS SQL Server, like we used to in 2005, won’t work today.
  • Since access rights are checked for every system request (including user and API requests), the highest possible read performance is crucial. That’s why all the data should reside in RAM.
  • The database must support secondary indexes that will allow performing complex queries on user lists — for example, quickly obtaining all users whose rating is between 10 and 25 points or something along these lines.
  • The system should, ideally, support queues, as most projects need this mechanism.

I’d like to point out that Tarantool nicely combines an in-memory database for caching with a full-blown NoSQL DBMS for persistently and safely storing data. Moreover, it meets all our requirements above and demonstrates decent performance. That said, after comparing these and other characteristics against several other products, we eventually opted for Tarantool. As I mentioned, it’s always better to handle a task with one good solution, instead of configuring endless systems.

One important thing about the authorization and authentication microservice: you can’t store information about users and groups in an on-disk DBMS and then simply move it to the in-memory cache; in this case, the cache should be able to quickly and reliably get in sync with the main database. You don’t want a blocked user to remain active just because the cache and the database haven’t gotten synced yet, or a new user being unable to log in for the same reason. As a developer, I don’t fancy the idea of implementing such synchronization logic myself: its reliability is dubious and the risk of errors is high. The fact that Tarantool has a write-ahead log (WAL) and on-disk and RAM-resident data are always synced is a huge advantage. It’s probably one of the most important features, apart from performance considerations, that cemented our choice of Tarantool.

We’re approaching the part of the story where I tried to install and run Tarantool on Windows. It’s followed by the tutorial on how to interact with Tarantool from a .NET application.

Working with Tarantool on Windows

When Node.js was just gaining traction, it didn’t have a Windows version. For us Windows developers it was either patiently waiting in envy or moving to a Mac, which many of us did in time. Microsoft has come to realize that developers got used to macOS and Linux environments and there’s no luring them back. Even if the company does pull it off, the developers will still miss a lot of things. I don’t know if that’s what people at Microsoft thought, but Windows 10 users can now launch unchanged executable files from different Linux distributions (only Ubuntu was supported at first, but later SUSE was added as well) without emulation or virtualization via the Windows Subsystem for Linux (WSL). The WSL implements Linux kernel calls, and minimal/pico processes allow users not to load ntdll.dll and not to do some other Windows-specific things. This functionality was introduced to Windows 10 Anniversary Update in the summer of 2016. However, this version is too glitchy, so if you decide to launch Tarantool this way, I recommend the latest builds from Insider Preview’s Fast Ring or Windows 10 Creators Update once it’s available. That’s exactly what I did when writing this article (I was using build 15031). In build 15031, Tarantool is quite stable overall, even though it throws non-critical errors now and then. Earlier Windows builds had issues even with launching Tarantool.

It goes without saying that the solution involving the WSL is described here only for the purposes of development — it won’t work on a production server running Windows Server due to the lack of native support. That’s why in this case we use Tarantool instances running in Docker containers deployed on Azure Container Service. Our ASP.NET Core microservices work the same way, so we didn’t have to introduce any new entities to the project. Visual Studio 2017 has built-in support for Docker containers, which significantly simplifies development. The beauty of Docker lies in the fact that it prevents vendor lock-in: we can easily migrate our microservices either to Digital Ocean or Amazon Lightsail.

Since Docker is used on production servers, it’s a good idea to use it in development as well. From my experience, Docker on Windows isn’t any worse than Docker on macOS. Naturally enough, you’ll need to use virtualization (same as on macOS), but it helps avoid numerous compatibility issues when deploying the project to production. Moreover, you don’t need the Insider Preview build or the latest Windows version to work with Docker.

That said, I’ll describe launching Tarantool in the WSL as a mere demonstration of the fact that it’s feasible (it’ll be useful for learning how Tarantool works as well; besides, it’ll come in handy if you can’t or don’t want to use Docker for some reason), but I recommend using Docker for development in real-life projects.

Tarantool in the Windows Subsystem for Linux

Well, it’s time to launch Tarantool on Windows with the WSL in five easy steps. It took me about 15 minutes, but a lot depends on your hardware and the quality of your Internet connection. As a reminder, I’m doing all this on Windows 10 Insider Preview build 15031.

1. Go to Start Menu > Settings > Update and Security > For developers and enable the Developer mode if it’s not enabled yet.

2. Launch PowerShell and execute the following command to enable the WSL:

Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux

Same as in step 1, a reboot will be needed.

3. In the console, execute the bash command. You’ll be prompted to download a user-mode Ubuntu distribution, create a Linux user and set a password. Once you’re done, you’ll see a tile for launching Bash on Ubuntu on Windows in the Start Menu.

Yay! Now we have full-fledged Bash and can run any software available on Ubuntu. But most importantly, the apt-get command is supported. Let’s take advantage of it and install Tarantool.

4. Here’s the installation instruction for Ubuntu taken from the official site:

curl http://download.tarantool.org/tarantool/1.7/gpgkey | sudo apt-key add -
release=`lsb_release -c -s`
# install https download transport for APT
sudo apt-get -y install apt-transport-https
# append two lines to a list of source repositories
sudo rm -f /etc/apt/sources.list.d/*tarantool*.list
sudo tee /etc/apt/sources.list.d/tarantool_1_7.list <<- EOF
deb http://download.tarantool.org/tarantool/1.7/ubuntu/ $release main
deb-src http://download.tarantool.org/tarantool/1.7/ubuntu/ $release main
EOF
# install
sudo apt-get update
sudo apt-get -y install tarantool

Execute these commands in Bash, and — voila, Tarantool is installed. Now we can launch it.

5. To launch Tarantool, execute the command of the same name:

tarantool

Tarantool uses Lua as an embedded language, and you should see the interpreter prompt:

tarantool>

Lua’s peculiarity is that it’s a very simple programming language that literally takes 15 minutes to get hold of. From my experience, a 15-minute crash course is really enough to understand almost all the code that appears in Tarantool examples.

So, after you execute tarantool, you’ll see the Lua interpreter. Let’s now configure Tarantool by assigning it to port 3311 via Tarantool’s main library called box.

box.cfg({listen = 3311})

In this case, since the only parameter passed to the cfg function is a table with parameters (Lua tables are very similar to JavaScript objects and have nothing to do with tables in relational DBMSs), we can omit the brackets and simply write:

box.cfg{listen = 3311}

On to creating a space for storing data. Tarantool’s spaces are analogous to MongoDB’s collections or tables in other DBMSs.

box.schema.space.create(‘customers’)

Below, we’re setting a primary index on the first field (we can also create secondary and composite indexes):

box.space.customers:create_index(‘primary’, {type = ‘tree’, parts = {1, ‘UNSIGNED’}})

The colon in customers:create_index is used for emulating object-orientedness and correctly passing the self parameter. The following two calls are the same (notice the period in one call and the colon in the other):

box.space.customers:select({})

and

box.space.customers.select(box.space.customers,{})

Let’s now insert a couple of records into the customers space: one by explicitly specifying the primary key, the other with the automatic increment:

box.space.customers:insert{1, ‘Sergey’, ‘Moscow’}
box.space.customers:auto_increment{‘Ivan’, ‘San-Francisco’}

Getting all the records from the space:

box.space.customers:select{}

We see the two newly added records in the YAML format:

If you’re just getting started with Tarantool, I recommend completing a tutorial, which is launched as follows:

tutorial()

Also, here are a few great articles that will help you get up to speed:

To reiterate, we got Tarantool working on Windows with the help of the WSL in five easy steps. Tarantool works as a Windows process, without using virtualization. Let’s now see how we can achieve a similar result by using Docker.

Tarantool in Docker on Windows

From the infrastructure standpoint, I think Docker is the best thing that’s happened to the IT industry in a decade: you can play with almost any product without any lengthy configurations, simply by executing docker run. And Tarantool is no exception. So just install Docker for Windows if you haven’t already and execute the following command in the console:

docker run — name mytarantool -d tarantool/tarantool:1.7

If you don’t have a Tarantool 1.7 image, it will be automatically downloaded, and a new container created for this image will be run. That’s it, we’ve launched Tarantool! The -d(etached) parameter specifies we want to run the container in the background, so all you see in the console after executing the command above is the container ID.

We can now connect to our new Tarantool instance via its own console:

docker exec -it mytarantool console

We’re connecting to the container called mytarantool and, once inside, executing the console command in the interactive mode. It brings up the Tarantool console, where you can execute any commands.

As you see, with Docker it took us one command to run Tarantool and one more command to connect to it. It’s even simpler than the previous example with the WSL. That’s why I like Docker much better. Besides, in Docker Tarantool runs in the same environment it would run in on a production server.

A Tarantool instance is currently up and running in Docker. It even stores its data somewhere, which is not bad. The problem is we don’t know where this data is stored. We need to specify a location on the Windows host where Tarantool will store logs and snapshots. Also, Tarantool should be made accessible from Windows on a certain port (say, 3301). This setup is required in order for our .NET application to be able to connect to Tarantool via localhost:3301.

Before we do this, let’s stop and remove the existing container:

docker stop mytarantool && docker rm mytarantool

We’ll create a C:\tarantool\data folder for holding Tarantool’s logs and snapshots. The name and location of the folder are arbitrary. Just make sure the disk containing this folder can be accessed by Docker: click the Docker icon in the system tray and select Settings. Go to Shared Drives and select the necessary check boxes.

Once done, let’s run a new Docker container with Tarantool.

docker run — name mytarantool -d -p 3301:3301 -v c:/tarantool/data:/var/lib/tarantool tarantool/tarantool:1.7

We’re linking the C:\tarantool\data folder on Windows to /var/lib/tarantool in the container — this is where, according to the container settings, data is stored. After we run the container, we can see a log file and a snapshot appear in the C:\tarantool\data folder.

Now Tarantool can be accessed by our .NET application (or any other) from Windows via localhost:3301. The only problem is that it’s currently empty. Let’s create a space and, if needed, insert some data there. We’ll also need to create users and assign them privileges. Doing it manually in the console is not a good idea — let’s write a Lua initialization script instead. Creating a full-blown Lua initialization script is covered in detail in the next section; for now, we’ll make do with the bare minimum, which we’ll gradually be improving upon. Let’s create a C:\tarantool\app folder with an app.init.lua file inside it. Configuration details are not important for now. Below is a Lua script you need to create at a minimum:

#!/usr/bin/env tarantool
box.cfg
{
pid_file = nil,
background = false,
log_level = 5
}

Also, we don’t want to type in complex commands every time we launch a Docker container, so we can take advantage of Docker Compose and create docker-compose.yml, a YAML file sitting in C:\tarantool\ that holds the whole configuration.

version: ‘2’
services:
tarantool:
container_name: mytarantool
image: tarantool/tarantool:1.7
command: tarantool /usr/local/share/tarantool/app.init.lua
ports:
— 3301:3301
volumes:
— c:/tarantool/app:/usr/local/share/tarantool
— c:/tarantool/data:/var/lib/tarantool

Here, we’re creating a tarantool service called mytarantool from the tarantool/tarantool:1.7 image. We’re also passing our initialization script app.init.lua as a runtime argument and making Tarantool accessible on port 3301. Finally, we’re linking the app and data folders on Windows to the Docker container.

We can now launch Tarantool with a simple Docker Compose command:

docker-compose -f C:/tarantool/docker-compose.yml up -d

The following command stops and removes the Tarantool container:

docker-compose -f C:/tarantool/docker-compose.yml down

If you’re currently in the folder containing the docker-compose.yml file, you don’t need to specify the file path:

cd C:/tarantool/
docker-compose up -d

So, we’ve set up a single-command Tarantool launch with docker-compose and created a simple initialization script. The folder structure is as follows:

It’s time to write a more advanced initialization script that creates a user and a space and loads test data.

Creating an initialization script

Our initialization script will perform several operations. First, it’ll create a user called operator with a password 123123. This user will be given read, write and execute permissions. Second. The script will create a users space and three indexes: unique index on the first field (indexing is one-based in Lua) that holds the user ID as a GUID string, unique index on the third field that contains the login, and non-unique index on the fifth field that stores the user’s rating as some number.

A user, a space and indexes are created inside the init function. The load_data function adds three records the the users space. The box.once function lets me call a certain function only once for the current database, which I find very handy. Below is the full code that goes inside the app.init.lua file:

#!/usr/bin/env tarantool
local log = require(‘log’)
local uuid = require(‘uuid’)
local function init()
box.schema.user.create(‘operator’, {
password = ‘123123’,
if_not_exists = true
})
  box.schema.user.grant(‘operator’, ‘read,write,execute’, 
‘universe’, nil, {
if_not_exists = true
})
  local users_space = box.schema.space.create(‘users’, {
if_not_exists = true
})
  users_space:create_index(‘primary_id’, {
if_not_exists = true,
type = ‘HASH’,
unique = true,
parts = {1, ‘STRING’}
})
  users_space:create_index(‘secondary_login’, {
if_not_exists = true,
type = ‘HASH’,
unique = true,
parts = {3, ‘STRING’}
})
  users_space:create_index(‘secondary_rating’, {
if_not_exists = true,
type = ‘TREE’,
unique = false,
parts = {5, ‘INT’}
})
end
local function load_data()
local users_space = box.space.users
  users_space:insert{uuid.str(), 
‘Ivan Ivanov’, ‘ivanov’,
‘iivanov@domain.com’, 10}
  users_space:insert{uuid.str(), 
‘Petr Petrov’, ‘petrov’,
‘ppetrov@domain.com’, 15}
  users_space:insert{uuid.str(), 
‘Vasily Sidorov’, ‘sidorov’,
‘vsidorov@domain.com’, 20}
end
box.cfg
{
pid_file = nil,
background = false,
log_level = 5
}
box.once(‘init’, init)
box.once(‘load_data’, load_data)

If you make changes to the app.init.lua file, restart the Docker container with the following Tarantool command:

docker-compose restart

Now we have a fully functional Tarantool instance holding some data that can be accessed by our .NET application.

Working with Tarantool in .NET/C#

The most popular library for working with Tarantool in .NET applications is ProGaudi/tarantool-csharp, which I’ll be using in this section.

This library’s GitHub repository has a great example of an ASP.NET Core application that uses Docker and works with Tarantool. This example features configuring Tarantool with Docker Compose, which we did in the previous section. I recommend downloading and studying this example when you have a chance. Let’s move on to creating a simple console application.

All the code and projects used in this article are available on GitHub.

Create a new console application in Visual Studio and install tarantool-csharp via NuGet. To do this, in the Solution Explorer window, click Manage NuGet packages, then find and install the package called Tarantool.CSharp.

Selecting records

It’s time to issue the first Tarantool request from the C# code. We’ll be querying the instance we launched in the Docker container and configured with the app.init.lua script in the previous section.

In the script, we’re creating a user called operator with a password 123123. Below is the code snippet that establishes a Tarantool connection, obtains the users space and the primary_id primary index and selects all records by this index.

using System;
using System.Threading.Tasks;
using ProGaudi.Tarantool.Client;
using ProGaudi.Tarantool.Client.Model;
using ProGaudi.Tarantool.Client.Model.Enums;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
DoWork().Wait();
}
    static async Task DoWork()
{
using (var box = await Box.Connect(
“operator:123123@localhost:3301”))
{
var schema = box.GetSchema();
        var space = await schema.GetSpace(“users”);
var primaryIndex = await space.GetIndex(“primary_id”);
        var data = await
primaryIndex.Select<TarantoolTuple<string>,
TarantoolTuple<string, string, string, string, long>>(
TarantoolTuple.Create(String.Empty), new SelectOptions
{
Iterator = Iterator.All
});
        foreach (var item in data.Data)
{
Console.WriteLine(item);
}
}
}
}
}

The code is mostly self-explanatory, so I’ll just dwell on the Select function a little. In this function, it’s necessary to specify the type of the key for selecting records and the type of the return value. Since our identifiers are string GUIDs, the key type is TarantoolTuple<string>.

Information about a user includes their ID, name, login, email and rating — four string fields and one numeric. So the return value type is TarantoolTuple<string, string, string, string, long>.

With all the types specified, the Select function looks like this:

primaryIndex.Select<TarantoolTuple<string>,
TarantoolTuple<string, string, string, string, long>>(…)

After that some arguments are passed to the Select function. The first argument is a tuple containing an empty string — TarantoolTuple.Create(String.Empty). We could pass the ID of a certain user instead if we needed to select records by the user ID, but passing an empty string as the key allows us to retrieve all the records at once. Specifying the Iterator.All iterator in a parameter of the SelectOptions type also signals we wish to retrieve all the records.

In this example, a new Tarantool connection is created each time the DoWork function is called. In fact, there’s no need to create a new connection for each function call — a single connection can very well be reused.

Launch the application and make sure you see in the console all the users currently residing in the users space.

Inserting new records

Let’s add a new record to the users space. To do this, we’ll create a tuple containing five fields and generate a new GUID that will be stored in the first field.

await space.Insert(TarantoolTuple.Create(Guid.NewGuid().ToString(), 
“Vladimir Vladimirov”, “vvladimirov”, “vvladimirov@domsin.com”, 10L));

Updating records

Updating records is almost identical to selecting them. Moreover, the Update function returns the updated value, which makes it all the more similar to Select. In the code snippet below, we’re assigning a new rating value to a user with a specified ID.

var updatedData = await space.Update<TarantoolTuple<string>,
TarantoolTuple<string, string, string, string, long>>(
TarantoolTuple.Create(“4e574d2f-1c82–4e14-aba8–95c6412e357c”),
new UpdateOperation[]{ UpdateOperation.CreateAssign<long>(4, 47L) });

Selecting records by secondary key

Apart from primary keys, we can use secondary keys for querying the database. In the example below, we’re selecting a record with the value of petrov (sits in the login field) by the secondary_login secondary index:

var loginIndex = await space.GetIndex(“secondary_login”);
var users = await loginIndex.Select<TarantoolTuple<string>,
TarantoolTuple<string, string, string, string, long>>(
TarantoolTuple.Create(“petrov”));
var petrov = users.Data;

Let’s select all the users with the rating value greater than or equal to (Iterator.Ge) 15.

var ratingIndex = await space.GetIndex(“secondary_rating”);
var ratingUsers = await ratingIndex.Select<TarantoolTuple<long>,
TarantoolTuple<string, string, string, string, long>>(
TarantoolTuple.Create(15L), new SelectOptions
{
Iterator = Iterator.Ge
});

Calling Lua functions

Aside from manipulating data from the C# code with Select, Insert, Update and other functions, you often need to process this data as close to it as possible. The closest you can get is the Tarantool server. Let’s create a Lua function called update_rating that increments users’ rating right on the server. It takes just a few lines of code. The function iterates over the users space with the help of the pairs() function and increments each record’s rating value by one. Add the code below to the end of the app.init.lua file and restart the Docker container running Tarantool. Sure enough, restarting a container is not the best option when on a production server, but when developing and debugging your application it’s definitely the fastest one.

function update_rating()
for k,v in box.space.users:pairs() do
box.space.users:update(v[1], {{‘+’, 5, 1}})
end
end

Let’s now call the update_rating function from C#:

await box.Call(“update_rating”);

Add the function call to your application and run it to make sure the rating gets updated as expected.

Being able to write your own Lua functions is one of Tarantool’s advantages. These functions can perform complex data processing without the need to pass this data back and forth between Tarantool and the client application. Besides, Tarantool itself is an application server, so you can write microservices that work only on Tarantool without using any other programming language like C#, Java, Go or PHP. Usually, such microservices expose a REST API that can also be implemented in Lua alone.

Now that I’ve covered the basics of interacting with Tarantool from a .NET application, I’d like to share my impressions of ProGaudi/tarantool-csharp

Impressions of ProGaudi/tarantool-csharp

After using ProGaudi/tarantool-csharp my impressions are somewhat ambiguous. On the one hand, the library enables you to perform almost any request a regular application might need to. If something’s not supported, you can always create a custom Lua function that can easily be called from the C# code. On the other hand, the library is too low-level, which entails writing lots of template code. And it clearly lacks good object-relational mapping (ORM) that would allow writing code not in terms of tuples but classes. It got me thinking about what such ORM for Tarantool might look like. The idea is still in its infancy, so any suggestions are welcome. I’m picturing something along these lines:

public class User
{
public string Id { get; set; }
public string Name { get; set; }
public string Login { get; set; }
public string Email { get; set; }
public long Rating { get; set; }
}
private static async Task DoWork()
{
var box = (await Box.Connect(
“operator:123123@localhost:3301”)).Wrap();
  var space = box.Space(“users”);
var primaryIndex = space.Index(“primary_id”);
var ratingIndex = space.Index(“secondary_rating”);
  var all = await primaryIndex.Select<User>();
var user = await
primaryIndex.Select<User>(“f73ee542–7d3e-4dec-b7c6-ce5dc7a02920”);
  var top = await ratingIndex.Select<User>(20, Iterator.Ge);
var low = await ratingIndex.Select<User>(20, Iterator.Lt);
}

Here you can find the code that allows writing this way. Just be warned: the prototype is half-baked and its only purpose is to show what the API might look like. That said, I definitely wouldn’t use this code in a real-life project if I were you. I’m planning to put a full-blown ORM prototype in a GitHub repo in the nearest future.

Creating REST services in Tarantool

When working with Tarantool, you can’t help thinking that Tarantool alone is enough to solve a lot of problems and there’s no need to, say, write any additional C# code. Take the microservice described in this article. It needs to expose a REST API callable by other microservices. The initial idea was to implement this microservice as an ASP.NET Core application that uses the ASP.NET Web API and queries Tarantool. But it turned out the ASP.NET Core application does nothing more than pass data between microservice users and Tarantool. That’s why we can get Tarantool to expose a REST API to microservice users instead. Let’s see how it can be done.

In this example, the REST service will support only one GET request — /users. Also, we’ll create a beautiful main page that lists all the users. I’d like to point out here that Tarantool makes creating administrator panels for microservices a breeze.

The REST service will be available on port 8080, so we need to make the Docker container’s port 8080 accessible from Windows. Add the following port binding to docker-compose.yml:

ports:
— 3301:3301
— 8080:8080

Let’s now use Tarantool’s http.server module readily available in our Docker container to run the server. To do this, add the code below to the app.init.lua file:

local function users_handler(self)
local data = {
users = {},
count = box.space.users:len()
}
  for k, v in box.space.users:pairs{} do
table.insert(data.users, {
id = v[1],
user = v[2] .. ‘ (‘ .. v[3] .. ‘)’ ,
rating = v[5]
})
end
  return self:render{ json = data }
end
local httpd = require(‘http.server’)
local server = httpd.new(nil, 8080, {
app_dir = ‘/usr/local/share/tarantool/’
})
server:route({ path = ‘/’, file = ‘index.html.el’ })
server:route({ path = ‘/users’ }, users_handler)
server:start()

Here, we’re defining two paths: site root (/) and /users. The site root will correspond to the index.html.el (el stands for embedded Lua) file; for /users, a users_handler function will be called that generates data: the number of records in the users space and an array of users that contains a user’s ID, name (followed by their login in brackets) and rating. The generated data is then sent out in the JSON format that looks like this:

{
“count”:3,
“users”:[
{
“user”:”Ivan Ivanov (ivanov)”,
“rating”:10,
“id”:”c371b2b1–5090–450b-8a5d-7dba87ad5116"
},
{
“user”:”Petr Petrov (petrov)”,
“rating”:15,
“id”:”b4f80aad-f3c2–42b0–8088–84846aef9997"
},
{
“user”:”Vasily Sidorov (sidorov)”,
“rating”:20,
“id”:”07c82a50–4d8c-489a-9cc1-b4fdd95afbd8"
}
]
}

Now we need to create the index.html.el file and put it into the new C:\tarantool\app\templates folder. The folder structure should look as follows:

Let’s call the REST API exposed by /users from the index.html.el file and display all the users in a table.

<!DOCTYPE html>
<html lang=”en”>
<head>
<title>Users Dahsboard</title>
  <link rel=”stylesheet” 
href=”https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
</head>
<body>
<div class=”container”>
<div class=”page-header”>
<h1>Users Dasbboard</h1>
</div>
<table class=”table” id=”tblUsers”>
<thead>
<tr>
<th>#</th>
<th>Id</th>
<th>User</th>
<th>Rating</th>
</tr>
</thead>
</table>
</div>
<script>
fetch(‘/users’).then(resp => {
resp.json().then(data => {
        function createCell(tr, txt){
tr.insertCell().appendChild(
document.createTextNode(txt));
}
        let tblUsers = document.getElementById(‘tblUsers’);
        for(let i=0;i<data.users.length;i++){
var tr = tblUsers.insertRow();
createCell(tr, i + 1);
createCell(tr, data.users[i].id);
createCell(tr, data.users[i].user);
createCell(tr, data.users[i].rating);
}
});
});
</script>
</body>
</html>

I’m not going to give a detailed comment on the JavaScript code, as it’s out of the scope of this article, and will just say that it uses the Fetch API.

That’s it, the REST API for our microservice is ready. Now you can restart the Docker container and get a JSON file with all the users. Postman is a handy tool for that.

Also, when you visit the main page, you’ll see the administrator panel we created.

Wind-up

Like I said at the beginning of this article, my experience with Tarantool proved to be quite positive. It’s a well-established product that you can definitely use in your projects, including those developed on Windows. What I liked best about Tarantool is that it combines the features of several products: Tarantool alone is enough to handle a wide variety of tasks — no need to install and maintain any additional software, which is great.

In this article, I didn’t mention several interesting topics, such as queues and replication. That’s why I think it’s important to keep in mind that Tarantool is much more versatile than I described.

The downside is that interacting with Tarantool from C# isn’t as simple as one might wish. But I’m set on resolving this issue by implementing full-fledged ORM.

All the code featured in this article is available in this GitHub repo.

Thanks for taking the time to read my article!

Hacker Noon is how hackers start their afternoons. We’re a part of the @AMI family. We are now accepting submissions and happy to discuss advertising & sponsorship opportunities.
If you enjoyed this story, we recommend reading our latest tech stories and trending tech stories. Until next time, don’t take the realities of the world for granted!

More by Vadim Popov

More Related Stories