My First REST API » Integrate Services And Services

My First Feature » Integrate Services And Services


Integrate Services And Services

Our pg Service is in a good shape: it connects to PostgreSQL and it provides a mean for other Features to integrate into it.

Now it is time to consider how could this Service integrate with other Services and extend their functionalities.

For example, we use service-fastify to erogate a RESTful interface. Wouldn't be nice if a route handler could access the query() utility and actively pull data from the db?

Something like:

registerAction({
  target: '$FASTIFY_ROUTE',
  handler: {
    method: 'GET',
    url: '/',
    handler: async (request, reply) => {
      const data = request.pg.query('SELECT * FROM "public"."users"');
      reply.send(data.rows);
    },
  },
});

Well, this is possible when different Services offer enough hooks so to allow for Service composition. service-fastify is one of those services.

Decorate Fastify's App

In order to achieve our goal, we would need to decorate Fastify's instance with the query() utility from our Service.

That is easily done in our Service's manifest /pg/index.js:

registerAction({
  target: '$FASTIFY_PLUGIN',
  handler: ({ decorateRequest }, { getContext }) => {
    const query = getContext('pg.query');
    decorateRequest('pg', { query });
  },
});

💻 Live on CodeSandbox:
https://codesandbox.io/s/080-integrate-services-and-services-6zc8x?file=/src/pg/index.js:1893-2092


This Integration Action uses API methods to fetch a reference from the App's context and pipe it into the Fastify's request object.

👉 INTEGRATIONS SIMPLY CONNECT THE DOTS 👈

Service Order Matter

One cool thing about the getContext() API is that it throws an error in case the desired path does not exits.

This is important beause it let us write data paths as strings, but it will validate their correctness, majestically reducing runtime errors due to typos.

In our small Integration Action we want to access pg.query from the context, but that context gets created during $INIT_SERVICE. Interestingly enough, $FASTIFY_PLUGIN is also executed during $INIT_SERVICE.

This is a classic racing condition where it is important that pg gets initialized before service-fastify.

One simple solution would be to list pg as first service in our App's manifest:

forrestjs.run({
  services: [pg, serviceFastify],
});

But we don't really like the idea to rely on our developers to remember that. It's not really elegant!

Actions Priority

A much better solution is to use the priority option when we register the pg's $INIT_SERVICE Action, so to make sure it happens before Fastify's initialization.

In our Service Manifest /pg/index.js:

registerAction({
  target: '$INIT_SERVICE',
  handler: () => {},

  // Make sure this initialization happens before Fastify's
  priority: 1,
});

💻 Live on CodeSandbox:
https://codesandbox.io/s/080-integrate-services-and-services-6zc8x?file=/src/pg/index.js:964-1037


Simple, right?

Every Service or Feature has a default priority of 0.
By setting a custom priority we an easily ensure the correct execution order for our actions.

👉 This is something that should be used carefully and only in extreme conditions when you really know what you are doing.

🔥 Setting execution priorities introduces interlaced knowledge between Services or Features, effectively breaking the decoupled modules assumption.

🦸 WITH GREAT POWER COMES GREAT RESPONSIBILITY

Pull Users From the DB

Now that we have our pg service happily integrating with Fastify, we can modify the Users Feature so to use the db instead of the in-memory data store.

First we can modify the list users handler so to pull data from the db in /users/routes/list-users.js:

const GET_USERS = `SELECT * FROM "public"."users"`;

module.exports = async (request, reply) => {
  const data = await request.pg.query(GET_USERS);
  return data.rows;
};

💻 Live on CodeSandbox:
https://codesandbox.io/s/080-integrate-services-and-services-6zc8x?file=/src/users/routes/list-users.js


Insert Users into the DB

Our app has also a second route that manages adding users, also here we can easily reference the query() method to push data into our persistent state manager (cool name for "db").

Open /users/routes/add-users.js:

const ADD_USER = `
  INSERT INTO "public"."users"
  VALUES ( $1 )
  RETURNING *
`;

const handler = async (request, reply) => {
  try {
    const data = await request.pg.query(ADD_USER, [request.query.name]);
    reply.send(data.rows);
  } catch (err) {
    reply.status(409).send(err.message);
  }
};

💻 Live on CodeSandbox:
https://codesandbox.io/s/080-integrate-services-and-services-6zc8x?file=/src/users/routes/add-users.js


Cleanup the In-Memory State

The last step in moving towards PostgreSQL as state manager would be to cleanup the previously created in-memory store.

We can achieve this by simply removing the $INIT_FEATURE action from our Users Feature's manifest in /users/index.js.

results matching ""

    No results matching ""