📜 ⬆️ ⬇️

Introduction to Seneca.JS

Seneca - microservices toolkit for Node.JS. It provides plugins that take care of the basics of your application. This allows you to focus on real business logic. No need to worry about which database to use, how to structure components, or how to manage dependencies. Just start writing code.


You describe everything as action. Actions are invoked whenever they correspond to a set of properties. Your calling code does not know and does not care what action does the work. One JavaScript object is included, and the other exits, asynchronously.


What Seneca.js is not


I love concrete comparisons, and alas, I quickly make hasty generalizations that can end up hurting me. To avoid this, here is a list of what Seneca does not have:



So what is Seneca.js?


Now that you have read these three points, you are probably rather confused about what Seneca is. Having described this framework many times and using many different terms and phrases, I found this definition of Seneca most useful:


Seneca is a tool that divides an application into small actions.

This definition is a simplification of everything that Seneca gives, but this is the core of what Seneca performs. The only ambiguous term in this definition is the word "action", which is the basic element of the Seneca application, as well as the most important abstraction provided by the toolkit.


Actions


Action is a function that is identified by a JSON object. Actions are at the heart of Seneca, and to use Seneca, a developer must be able to think in terms of small functions that can be called from anywhere by their JSON identifiers. Actions are created using the seneca.add method:


seneca.add({role:'inventory', cmd:'find_item'}, function(args, done) { const itemId = args.id; // find item using any means necessary const item = byAnyMeansNecessary(itemId); done(null, item); }); 

Actions can have any granularity and any JSON template. You can have actions from {application: 'myApp', accomplish: 'everything'} in {module: 'addition', perform: 'onePlusOne', because: 'reasons'}. As a personal style and agreement found in all Seneca plugins, I try to save my actions in the format {role: 'namespace', cmd: 'action'}, where "namespace" is a logical grouping of several actions and "action" is the name of a specific actions that I want to define.


Calling actions can be performed using the seneca.act method:


 seneca.act({role:'inventory', cmd:'find_item', id:'a3e42'}, function(err, item) { if (err) return err; console.log(item); // Perform other actions with item }); 

I prefer to define related actions in the same file with the same role (for example, the todo-list.js file will have all tasks with the todo_list role). As a rule, I try to keep this file no more than 150 lines, and I try to make each definition of an action small enough to read without a scroll. Other people may have different preferences, but this size is convenient for me.


Why actions?


So now that you have a basic understanding of the Seneca base element, you are probably wondering about the goal of breaking the system up into action definitions and action calls. The goal here is to create acceptable boundaries between the components of your application that will make you think about a more modular architecture and avoid the desire to throw everything into one main file. As we will see shortly, after the application has been divided into actions, Seneca provides an abundance of tools to uncover actions, both in the form of HTTP microservices and URLs of web servers.


Action organization


Earlier, I mentioned how I store all related actions in one file, but did not describe how I attached these actions to a Seneca instance in another file. Seneca contains the seneca.use method, which finds the corresponding file or module and includes it in the Seneca instance. For example, if I had several actions that all related to inventory, I would create inventory.js and define all the actions there:


 /* inventory.js */ module.exports = function(options) { const seneca = this; seneca.add({role:'inventory', cmd:'find_item', find_item); seneca.add({role:'inventory', cmd:'create_item', create_item); //... other action definitions function find_item(args, done) { const itemId = args.id; // ... perform find done(null, item); } function create_item(args, done) { const itemName = args.name; // ... perform item creation done(null, item); } } /* server.js */ const seneca = require('seneca')(); seneca.use('./inventory.js'); seneca.act({role:'inventory', cmd:'create_item', name:'apple'}, function(err, item) { console.log(item); } 

Notice that through this we get a link to the seneca instance in inventory.js. After adding actions to the instance, we can invoke these actions in server.js. Common format for example inventory.js - call seneca.add in the upper part and then define the methods. This is not necessary, just this agreement is respected by several implemented plug-ins.


There are hundreds of plug-ins that can be used to perform predefined actions in an application — from integration with express to authentication and database access. The ecosystem is quite extensive and allows you to speed up writing applications as soon as you understand how to work with actions.


Next level: several processes


In the above example, you can already start the application using node server.js and everything will happen in one process.
However, what to do if you need to perform several processes, one of which would simply process the inventory actions and another process using them. Interprocess communication can be tricky for NodeJS applications, but Seneca only allows this behavior in two code changes.


We will leave inventory.js unchanged, using actions as a Seneca plugin. However, we will create two new files, one of which is inventory-service.js, which will start the service on port 10101 using seneca.listen and the other inventory-client.js file, which will use seneca.client to access the service and use it action.


 /* inventory-service.js */ const seneca = require('seneca')(); seneca.use('./inventory.js'); seneca.listen(); 

Now we can run node inventory-service.js in a separate process and it will listen on port 10101. In inventory-client.js we can write:


 /* inventory-client.js */ const seneca = require('seneca')(); seneca.client(); seneca.act({role:'inventory', cmd:'create_item', name:'apple'}, function(err, item) { console.log(item); } // … 

Running node inventory-client.js will do the same as server.js in the previous example, only now we have the ability to scale our application horizontally across several processes. This is a true advantage of using Seneca: since we have divided our application into small actions, these actions can be declared in different files or even different processes. With plugins available for web integration, actions can be performed even on individual hosts.


Based on the translation of this article.


Links



If there is interest, then I’m ready to describe how to integrate all this with Express, wrap it up in Docker and scale it.


')

Source: https://habr.com/ru/post/342292/


All Articles