EGF2 Guide - Adding Logic

There are situations when some actions need to be taken in response to a particular change in data. Change can be initiated by a user request, by some service working on a schedule or by any other source. In case actions that need to be done deal with data modifications (not sending notifications which we deal with in pusher) we implement such actions (called "rules") in logic service.

We will add rules for the following situations:

  1. When a new User/follows edge is created logic should create a corresponding edge User/followers.
  2. When a User/follows edge is removed logic should remove corresponding User/followers edge.
  3. When a new User/posts edge is created logic should create User/timeline edge for each User that follows the author of this Post (e.g. Users from User/followers edge). Here we have an example of "fan-out on write" approach.
  4. When a User/posts edge is removed logic should remove User/timeline edge for all followers of this User.
  5. When a new Post/offended edge is created logic needs to find all users in the system that have AdminRole and create a new edge AdminRole/offending_posts. It should check if an edge was already created before creation though - we may have multiple users offended by a single Post.

In our system documentation we will have all rules listed in a table in Logic Rules section.

Let's start with adding files with rules' implementations and then proceed with configuring the registry.

Rule 1, 2

Please create file logic/extra/user_follows.js with the following content:

"use strict";

const clientData = require("../components").clientData;

// rule #1
function onCreate(event) {  
    return clientData.createEdge(event.edge.dst, "followers", event.edge.src);
}

// rule #2
function onDelete(event) {  
    return clientData.deleteEdge(event.edge.dst, "followers", event.edge.src);
}

module.exports = {  
    onCreate,
    onDelete
};

Please note the use of clientData. clientData module provides easy and convenient way to interface with client-data service.

Rule 3, 4

File name: logic/extra/user_posts.js, content:

"use strict";

const clientData = require("../components").clientData;

// Rule #3
function onCreate(event) {  
    return clientData.forEachPage(
        last => clientData.getEdges(event.edge.src, "followers", {after: last}),
        followers => Promise.all(followers.results.map(follower =>
            clientData.createEdge(follower.id, "timeline", event.edge.dst)
        ))
    );
}

// Rule #4
function onDelete(event) {  
    return clientData.forEachPage(
        last => clientData.getEdges(event.edge.src, "followers", {after: last}),
        followers => Promise.all(followers.results.map(follower =>
            clientData.createEdge(follower.id, "timeline", event.edge.dst)
        ))
    );
}

module.exports = {  
    onCreate,
    onDelete
};

In rules 3 and 4 we have an example of paginating over pages of an edge with clientData.forEachPage.

Rule 5

This rule is a bit more interesting. We need to get a list of all admins in the system. Graph API has no way to get a list of objects that are not connected to some edge.

Let's define a new automatic ES index with sync and use search endpoint for this.

Add the following text to the /opt/sync/config.json:

...
"admin_role": {
    "object_type": "admin_role",
    "mapping": {
        "id": {"type": "string", "index": "not_analyzed"}
    }
}
...

Restart sync - we are ready for getting admins.

Now we need to implement the rule, file name: logic/extra/post_offended.js, content:

"use strict";

const clientData = require("../components").clientData;  
const searcher = require("../components").searcher;  
const log = require("../components").logger;

// Rule #5
function onCreate(event) {  
    return clientData.getGraphConfig().then(config =>
        clientData.forEachPage(
            last => searcher.search({object: "admin_role", count: config.pagination.max_count, after: last}),
            admins => Promise.all(admins.results.map(admin =>
                clientData.createEdge(admin, "offending_posts", event.edge.src)
                    .catch(err => {
                        log.error(err);
                    })
                ))
        )
    );
}

module.exports = {  
    onCreate
};

Please note here the way searcher module is used. searcher provides a way to interact with ElasticSearch without the need to work with ES driver.

We have prepared logic rule handlers, now we need to let logic know about them. Add the following lines in logic/extra/index.js file, in the handleRegistry map:

...
    "POST user/follows": require("./user_follows").onCreate,
    "DELETE user/follows": require("./user_follows").onDelete,
    "POST user/posts": require("./user_posts").onCreate,
    "DELETE user/posts": require("./user_posts").onDelete,
    "POST post/offended": require("./post_offended").onCreate
...

Restart logic service.

Even though EGF2 can not provide implementation for custom business rules for all systems and domains it does offer structure and a clear path for adding such rules. It suggests where implementation should live, how it should be connected to the rest of the system and it gives a recommendation on the way rules should be documented.

In the next post we will look into web app creation.

comments powered by Disqus