<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Sateesh Madagoni</title>
    <description>The latest articles on Forem by Sateesh Madagoni (@sateeshm).</description>
    <link>https://forem.com/sateeshm</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F70834%2F47746d2e-55d6-4960-9b4c-b2587718678e.jpg</url>
      <title>Forem: Sateesh Madagoni</title>
      <link>https://forem.com/sateeshm</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/sateeshm"/>
    <language>en</language>
    <item>
      <title>Orchestrate AWS Lambdas using MongoDB - Part 2</title>
      <dc:creator>Sateesh Madagoni</dc:creator>
      <pubDate>Wed, 15 Nov 2023 01:02:08 +0000</pubDate>
      <link>https://forem.com/sateeshm/orchestrate-aws-lambdas-using-mongodb-part-2-43h6</link>
      <guid>https://forem.com/sateeshm/orchestrate-aws-lambdas-using-mongodb-part-2-43h6</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Continuation to the &lt;a href="https://dev.to/sateeshm/orchestrate-aws-lambdas-using-mongodb-part-1-3po1"&gt;first part&lt;/a&gt;. This post assumes you are a developer with working knowledge on AWS, Lambda, EventBridge, MongoDB, NodeJs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Technical Implementation:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Design State-Machine&lt;/strong&gt;&lt;br&gt;
This step you need to figure out, how do you want to orchestrate your jobs and create an object like below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const stateMachine = {
  id: '1',
  purposeId: 'catch_the_fish',
  currentPhase: 'thinking',
  phases: {
    thinking: {
      steps: {
        `thinking`: {
          jobs: ['buy_fishing_gear'],
        },
        buy_fishing_gear: {
          jobs: ['rent_a_boat'],
        },
        rent_a_boat: {
          jobs: ['go_to_fishing_ground'],
        },
        go_to_fishing_ground: {
          jobs: ['cast_fishing_pole', 'drink_beer', 'catch_fish'],
        },
        catch_fish: {
          jobs: ['go_home'],
        },
      },
      finished: [],
      next: 'end',
      waitFor: [
        'buy_fishing_gear',
        'rent_a_boat',
        'go_to_fishing_ground',
        'cast_fishing_pole',
        'drink_beer',
        'catch_fish',
        'go_home',
      ],
    },
  },
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above example we are running &lt;code&gt;buy_fishing_gear&lt;/code&gt; after &lt;code&gt;thinking&lt;/code&gt; job is finished in a sequential manner, but we run &lt;code&gt;cast_fishing_pole&lt;/code&gt;, &lt;code&gt;drink_beer&lt;/code&gt; and &lt;code&gt;catch_fish&lt;/code&gt; in parallel after &lt;code&gt;go_to_fishing_ground&lt;/code&gt; is successful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Start the Process:&lt;/strong&gt;&lt;br&gt;
Creation of the statemachine could be anything from API to a cron job. Lets take API as an example as POST /process/catch-the-fish.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;router.post('/process/catch-the-fish', async (req, res, next) =&amp;gt; {
  const state = {}; // above mentioned state
  // Insert into state-machines
  const createdState = await db.collection('state-machines').insertOne(state);
  // Start the process by sending first event. As thinking is success we start the process of catching fish
  await db.collection('statuses').insertOne({
    stateId: createdState._id,
    status: 'success',
    job: 'thinking',
    date: new Date(),
    // additional parameters as needed
  });
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. State Machine Job:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exports.handler = async (event, ctx, callback) =&amp;gt; {
  // Mongodb Document from the trigger
  const document = event.detail.fullDocument;
  // Find the state machine
  const state = await db
    .collection('state-machines')
    .findOne({ _id: document.stateId });
  // if its ended do nothing
  if (state?.currentPhase === 'end') {
    callback();
  }
  // Update the job as finished for the state machine
  const currentPhase = state.phases[state.currentPhase];
  currentPhase.finished.push(document.job);
  await db
    .collection('state-machines')
    .findOneAndUpdate(
      { _id: document.stateId },
      { $set: { [`phases.${state.currentPhase}`]: currentPhase } }
    );
  const jobsRemaining = currentPhase.waitFor.filter(
    (s) =&amp;gt; !currentPhase.finished.includes(s)
  );
  // If there are no remaining jobs in the current phase
  if (jobsRemaining.length === 0) {
    // Update the currentPhase to be the next Phase.
    await db.collection('state-machines').findOneAndUpdate(
      { _id: document.stateId },
      {
        $set: {
          currentPhase: currentPhase.next,
        },
      }
    );
    // If the next phase is not end trigger the phase.
    if (currentPhase.next !== 'end') {
      await db.collection('statuses').insertOne({
        ...document,
        job: `${currentPhase.next}Start`,
        status: 'success',
      });
    }
  }
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Above, whenever a job succeeds this will update the finished jobs and check if the phase is finished and move onto next phase until it meets the end phase.&lt;br&gt;
&lt;strong&gt;4. Orchestrator Job:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exports.handler = async (event, ctx, callback) =&amp;gt; {
  ctx.callbackWaitsForEmptyEventLoop = false;
  const document = event.detail.fullDocument;
  const state = await db
    .collection('pipeline-state-machines')
    .findOne({ _id: document.stateId });
  const currentPhase = state[state.currentPhase];
  if (!document.stateId) {
    console.log('Cannot execute the pipeline without the state id:', state);
    return { success: false };
  }
  if (state.currentPhase === 'end') {
    console.log('Cannot execute the pipeline without the state id:', state);
    return { success: false };
  }
  if (!currentPhase) {
    console.log('Cannot execute the pipeline without the state id:', state);
    return { success: false };
  }
  const step = currentPhase.steps[document.job];
  if (step &amp;amp;&amp;amp; step.jobs &amp;amp;&amp;amp; step.jobs.length &amp;gt; 0) {
    await Promise.all(
      step.jobs.map(async (job) =&amp;gt; {
        const payload = {}
        const event = {
          FunctionName: job,
          InvocationType: 'Event',
          LogType: 'Tail',
          Payload: JSON.stringify(payload),
        };
        return lambda.invoke(event);
      })
    );
  }
  callback();
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above job receives the success event and finds the next jobs to trigger and invoke them.&lt;br&gt;
&lt;strong&gt;5. Notifications Job:&lt;/strong&gt;&lt;br&gt;
As above the job receives all the events so we can use the data and structure the message to send.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if there are multiple phases:&lt;/strong&gt;&lt;br&gt;
Then we just have to add another phase to existing state machine configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const stateMachine = {
  id: '1',
  purposeId: 'catch_the_fish',
  currentPhase: 'thinking',
  phases: {
    thinking: {
      // Prev things
    },
    cooking: {
      steps: {
        cookingStart: {
          jobs: ['clean'],
        },
        clean: {
          jobs: ['cook'],
        },
      },
      waitFor: ['cook', 'clean'],
      finished: [],
      next: 'end',
    },
  },
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I hope the above code and explanation gives you a way to implement your own solutions. For anymore details please do comment, I would be happy to help. Thanks.&lt;/p&gt;

</description>
      <category>eventbridge</category>
      <category>statemachine</category>
      <category>orchestration</category>
      <category>jobs</category>
    </item>
    <item>
      <title>Orchestrate AWS Lambdas using MongoDB - Part 1</title>
      <dc:creator>Sateesh Madagoni</dc:creator>
      <pubDate>Wed, 15 Nov 2023 01:01:30 +0000</pubDate>
      <link>https://forem.com/sateeshm/orchestrate-aws-lambdas-using-mongodb-part-1-3po1</link>
      <guid>https://forem.com/sateeshm/orchestrate-aws-lambdas-using-mongodb-part-1-3po1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This post assumes you are a developer with working knowledge on AWS, Lambda, EventBridge, MongoDB, NodeJs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Usecase&lt;/strong&gt;: Let's say you have several lambdas that you need to run some in parallel, in sequential and some in conditional, and their collective goal is to finish a task. To orchestrate these jobs we need a state-machine(&lt;a href="https://statecharts.dev/what-is-a-state-machine.html"&gt;https://statecharts.dev/what-is-a-state-machine.html&lt;/a&gt;), which ensures the jobs are run based on the given conditions and will terminated itself at the end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: For the similar problem, AWS Lambda provides a solution called step-functions where you could chain these lambdas, I felt its too difficult to establish and manage them, so I created a custom solution using MongoDB Triggers and AWS EventBridge. Now lets work on the process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Components&lt;/strong&gt;&lt;br&gt;
_1. Statemachine Configuration: A configuration which consists of all the phases and jobs to run in order. This will be saved to a collection &lt;code&gt;state-machines&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;2. Status Event&lt;/em&gt;: Whenever a job status changes, i.e, &lt;code&gt;success&lt;/code&gt;, &lt;code&gt;failed&lt;/code&gt;, &lt;code&gt;started&lt;/code&gt; we insert an event to database which will be consumed by other necessary jobs. This events will be saved &lt;code&gt;statuses&lt;/code&gt; collections. This is the collection where MongoDB trigger is configured.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;3. Statemachine Job&lt;/em&gt;: An additional job apart from the business jobs to update the state machine whenever a job completes or fails. This job only receives &lt;code&gt;success&lt;/code&gt; events.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;4. Orchestrator Job&lt;/em&gt;: An additional job apart from the business jobs to trigger the next jobs. This job only receives &lt;code&gt;success&lt;/code&gt; events.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;5. Notification Job&lt;/em&gt;: You could have another lambda to send messages to slack or any other messaging platform. This job only receives all events.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;A state machine will be created by either an API or any such equivalent method based on the requirements. And sends an &lt;code&gt;status&lt;/code&gt; event as the start of the process. Now this event is received by &lt;code&gt;statemachine_job&lt;/code&gt;, &lt;code&gt;orchestrator_job&lt;/code&gt; and &lt;code&gt;notifications_job&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;statemachine_job&lt;/code&gt; will update the corresponding statemachine with the finished job. Check if it has to either end the state machine or move onto next phase.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;orchestrator_job&lt;/code&gt; will find the next job to run based on the given configuration, until nothing left to run.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;notifications_job&lt;/code&gt; will send the current event message to configured messaging medium.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This cycle continues until all the phases finished &amp;amp; satisfied with finished jobs.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Set Up&lt;/strong&gt;: &lt;br&gt;
As mentioned we need to two collections &lt;code&gt;state-machines&lt;/code&gt;, &lt;code&gt;statuses&lt;/code&gt;. The model will be explained later. Create a trigger on &lt;code&gt;statuses&lt;/code&gt; collection, for &lt;code&gt;insert&lt;/code&gt; event such that whenever an item inserted into &lt;code&gt;statuses&lt;/code&gt; it will be sent to the AWS Eventbridge. Set up the database trigger for a collection &lt;code&gt;statuses&lt;/code&gt;. Follow this link to setup events for AWS Eventbridge &lt;a href="https://www.mongodb.com/docs/atlas/triggers/"&gt;https://www.mongodb.com/docs/atlas/triggers/&lt;/a&gt;.&lt;br&gt;
The above created event bridge will invoke &lt;code&gt;statemachine_job&lt;/code&gt;, &lt;code&gt;orchestrator_job&lt;/code&gt; and &lt;code&gt;notifications_job&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Lets work on the technical implementation on the &lt;a href="https://dev.to/sateeshm/orchestrate-aws-lambdas-using-mongodb-part-2-43h6"&gt;next part&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>orchestration</category>
      <category>lambda</category>
      <category>statemachine</category>
      <category>mongodb</category>
    </item>
    <item>
      <title>User Role Management in NodeJS, Express, MongoDB</title>
      <dc:creator>Sateesh Madagoni</dc:creator>
      <pubDate>Fri, 28 Feb 2020 18:06:47 +0000</pubDate>
      <link>https://forem.com/sateeshm/user-role-management-in-nodejs-express-mongodb-58mp</link>
      <guid>https://forem.com/sateeshm/user-role-management-in-nodejs-express-mongodb-58mp</guid>
      <description>&lt;p&gt;Problem: Multiple users in a system, allowed to do specific actions.&lt;/p&gt;

&lt;p&gt;Solution: There are multiple user role management packages in npm, but I want something easier, quicker. So I started solving it myself.&lt;/p&gt;

&lt;p&gt;Example: A blog with users - U, authors - A, admin - M&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create users with a field user_type.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Users.create({
name: 'User',
user_type: 'U'
})
Users.create({
name: 'Author',
user_type: 'A'
})
Users.create({
name: 'Author',
user_type: 'M'
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Assuming user logins managed using a jwt token. And sign the token including user_type, add a middleware to decode and save user data to req.user
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const decoded = await jwt.verify(token, process.env.JWT_SECRET);
req.user = {
    name: decoded.name,
    user_type: decoded.user_type
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Write another middleware to authenticate role.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const authenticateRole = (roleArray) =&amp;gt; (req, res, next) =&amp;gt; {
  if(!req.user) {
    return res.status(401).json({
      success: false,
      message: 'Session expired',
      code: 'SESSION_EXPIRED'
    });
  }
  const authorized = false;
//if user has a role that is required to access any API
  rolesArray.forEach(role =&amp;gt; {
   authorized = req.user.user_type === role;
  })
  if(authorized) {
    return next();
  }
  return res.status(401).json({
    success: false,
    message: 'Unauthorized',
  })
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Finally use the authenticateRole middleware in the API access.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//This is accessed by only Admin user
route.get('/users', authenticateRole(['M']), handler)
//This is accessed by anyone
route.get('/posts', authenticateRole(['M','U','A']))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;em&gt;I am trying to enhance this idea as my needs.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>expressjs</category>
      <category>roleaccess</category>
      <category>apiaccess</category>
      <category>middleware</category>
    </item>
  </channel>
</rss>
