<?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>DEV Community: Alfian Rivaldi</title>
    <description>The latest articles on DEV Community by Alfian Rivaldi (@alfianriv).</description>
    <link>https://dev.to/alfianriv</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%2F770505%2Fae993fc9-dba5-4b33-b8bc-88ebd554f58d.jpeg</url>
      <title>DEV Community: Alfian Rivaldi</title>
      <link>https://dev.to/alfianriv</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alfianriv"/>
    <language>en</language>
    <item>
      <title>Easy way to process waterfall functions</title>
      <dc:creator>Alfian Rivaldi</dc:creator>
      <pubDate>Wed, 03 Jul 2024 15:48:40 +0000</pubDate>
      <link>https://dev.to/alfianriv/easy-way-to-process-waterfall-functions-g0p</link>
      <guid>https://dev.to/alfianriv/easy-way-to-process-waterfall-functions-g0p</guid>
      <description>&lt;p&gt;Have you ever encountered a process that flows like a waterfall? This is an example.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F843lhsee8voqibedzfgb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F843lhsee8voqibedzfgb.png" alt="Waterfall flow" width="238" height="912"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even though we only hit 1 endpoint, we have to process all of them. &lt;/p&gt;

&lt;p&gt;Yes, that's easy, but what if step 2 fails and we want everything to be repeated. But the data has already entered the database? Just delete it anyway, but it's lazy to handle it all.&lt;/p&gt;

&lt;p&gt;Maybe the method I provide can make all of that easier, even if there are dozens of steps.&lt;/p&gt;

&lt;p&gt;Disclaimer first, I implement this on nestjs. And I hope those of you who read already understand and are proficient in using nestjs.&lt;/p&gt;

&lt;p&gt;We first prepare the base service, which contains the execute command to run all the steps in order.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Injectable } from '@nestjs/common';
import { FALLBACK_STEP_METADATA_KEY } from 'src/decorators/fallback.decorator';
import { STEP_METADATA_KEY } from 'src/decorators/step.decorator';
import { uid } from 'uid';

@Injectable()
export class WaterfallService {
  private steps: { methodName: string; order: number }[] = [];
  private fallbacks: { methodName: string; order: number }[] = [];

  constructor() {
    this.collectSteps();
  }

  private collectSteps() {
    const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(this));
    methods.forEach((methodName) =&amp;gt; {
      const order = Reflect.getMetadata(STEP_METADATA_KEY, this, methodName);
      if (order !== undefined) {
        this.steps.push({ methodName, order });
      }

      const fallbackOrder = Reflect.getMetadata(
        FALLBACK_STEP_METADATA_KEY,
        this,
        methodName,
      );
      if (fallbackOrder !== undefined) {
        this.fallbacks.push({ methodName, order: fallbackOrder });
      }
    });

    this.steps.sort((a, b) =&amp;gt; a.order - b.order);
    this.fallbacks.sort((a, b) =&amp;gt; a.order - b.order);
  }

  async executeSteps(params?) {
    const eventId = uid(6);
    let executedSteps = [];
    let returnedData: any;
    try {
      for (const step of this.steps) {
        let paramPassed = params;
        if (step.order &amp;gt; 1) {
          paramPassed = returnedData;
        }
        const result = await (this as any)[step.methodName](
          eventId,
          paramPassed,
        );
        returnedData = result;
        executedSteps.push(step);
      }
    } catch (error) {
      await this.executeFallbacks(executedSteps, eventId);
      throw error; // Re-throw the error after handling fallbacks
    }
  }

  private async executeFallbacks(executedSteps, eventId) {
    // Execute fallbacks in reverse order
    for (let i = executedSteps.length - 1; i &amp;gt;= 0; i--) {
      const step = executedSteps[i];
      const fallback = this.fallbacks.find((f) =&amp;gt; f.order === step.order);
      if (fallback) {
        await (this as any)[fallback.methodName](eventId);
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we will create a decorator to make it easier for the execute function to run the steps in the order we want.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import 'reflect-metadata';

export const STEP_METADATA_KEY = 'step_order';

export function Step(order: number): MethodDecorator {
  return (target, propertyKey, descriptor) =&amp;gt; {
    Reflect.defineMetadata(STEP_METADATA_KEY, order, target, propertyKey);
  };
}

export const FALLBACK_STEP_METADATA_KEY = 'fallback_step_order';

export function Rollback(order: number): MethodDecorator {
  return (target, propertyKey, descriptor) =&amp;gt; {
    Reflect.defineMetadata(FALLBACK_STEP_METADATA_KEY, order, target, propertyKey);
  };
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last one we implement into our service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { BadRequestException, Injectable } from '@nestjs/common';
import { WaterfallService } from './commons/waterfall/waterfall.service';
import { Step, Rollback } from './decorators/step.decorator';

@Injectable()
export class AppService extends WaterfallService {
  @Step(1)
  async logFirst(eventId) {
    console.log('Step 1 [eventId]:', eventId);
  }

  @Rollback(1)
  async fallbackFirst(eventId) {
    console.log('Rollback 1 [eventId]:', eventId);
  }

  @Step(2)
  async logSecond(eventId, data) {
    console.log('Step 2 [eventId]:', eventId);
  }

  @Rollback(2)
  async fallbackSecond(eventId) {
    console.log('Rollback 2 [eventId]:', eventId);
  }

  @Step(3)
  async logThird(eventId) {
    console.log('Step 3 [eventId]:', eventId);
  }

  @Rollback(3)
  async fallbackThird(eventId) {
    console.log('Rollback 3 [eventId]:', eventId);
  }

  async execute() {
    await this.executeSteps();
    return 'Step Executed';
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now in the controller don't forget to call the execute function, like this example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello() {
    return this.appService.execute();
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run the nestjs. For the test hit &lt;code&gt;[GET]http://localhost:3000&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Later in the log terminal it will be like this&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo3bzo865nqbpm2znxkle.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo3bzo865nqbpm2znxkle.png" alt="Example log terminal" width="800" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now it's easy, right? You just add as many function steps as you need.&lt;/p&gt;

&lt;p&gt;Now what if in step 3 there is an error and want to rollback the previous processes that have been run?&lt;br&gt;
Take it easy, as long as there is a function with the decorator &lt;code&gt;@Rollback(number)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The function will run if an error occurs. For example, here's an example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { BadRequestException, Injectable } from '@nestjs/common';
import { WaterfallService } from './commons/waterfall/waterfall.service';
import { Step, Rollback } from './decorators/step.decorator';

@Injectable()
export class AppService extends WaterfallService {
  @Step(1)
  async logFirst(eventId) {
    console.log('Step 1 [eventId]:', eventId);
  }

  @Rollback(1)
  async fallbackFirst(eventId) {
    console.log('Rollback 1 [eventId]:', eventId);
  }

  @Step(2)
  async logSecond(eventId, data) {
    console.log('Step 2 [eventId]:', eventId);
  }

  @Rollback(2)
  async fallbackSecond(eventId) {
    console.log('Rollback 2 [eventId]:', eventId);
  }

  @Step(3)
  async logThird(eventId) {
    throw new BadRequestException('Something error in step 3');
  }

  @Rollback(3)
  async fallbackThird(eventId) {
    console.log('Rollback 3 [eventId]:', eventId);
  }

  async execute() {
    await this.executeSteps();
    return 'Step Executed';
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Later, if it is run again, the results will be like this&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvqy54z3ot8kcx2zt9hte.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvqy54z3ot8kcx2zt9hte.png" alt="Example log terminal" width="800" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I intentionally add eventId in each step, if there is a case you insert data into the database, also save the eventId to identify that the data has the eventId owner. So when you rollback and want to delete the data, you don't get confused about which data is being rolled back.&lt;/p&gt;

&lt;p&gt;For the example case of data in the first function return that is passed to the next function, just return the function, then the return will be passed to the next function. This is the example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { BadRequestException, Injectable } from '@nestjs/common';
import { WaterfallService } from './commons/waterfall/waterfall.service';
import { Step, Rollback } from './decorators/step.decorator';

@Injectable()
export class AppService extends WaterfallService {
  @Step(1)
  async logFirst(eventId, data) {
    console.log('Step 1 [eventId]:', eventId);
    console.log('Step 1 [data]:', data);

    return {
      step: 1,
      message: 'this data from step 1',
    };
  }

  @Rollback(1)
  async fallbackFirst(eventId) {
    console.log('Rollback 1 [eventId]:', eventId);
  }

  @Step(2)
  async logSecond(eventId, data) {
    console.log('Step 2 [eventId]:', eventId);
    console.log('Step 2 [data]:', data);

    return {
      step: 2,
      message: 'this data from step 2',
    };
  }

  @Rollback(2)
  async fallbackSecond(eventId) {
    console.log('Rollback 2 [eventId]:', eventId);
  }

  @Step(3)
  async logThird(eventId) {
    throw new BadRequestException('Something error in step 3');
  }

  @Rollback(3)
  async fallbackThird(eventId) {
    console.log('Rollback 3 [eventId]:', eventId);
  }

  async execute() {
    const data = { step: 0, message: 'this data from initial function' };
    await this.executeSteps(data);
    return 'Step Executed';
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the terminal will look like this &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fevft6twtg2d4jtywpege.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fevft6twtg2d4jtywpege.png" alt="Example log terminal" width="800" height="171"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I added a repo example &lt;a href="https://github.com/alfianriv/waterfall-flow-nestjs"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That's all my method of running the waterfall function, Maybe if there are questions and collaboration, you can contact me.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Stop using one table for your client's</title>
      <dc:creator>Alfian Rivaldi</dc:creator>
      <pubDate>Thu, 27 Jun 2024 05:31:49 +0000</pubDate>
      <link>https://dev.to/alfianriv/stop-using-one-table-for-your-clients-4d67</link>
      <guid>https://dev.to/alfianriv/stop-using-one-table-for-your-clients-4d67</guid>
      <description>&lt;p&gt;Do you have a saas? And you have a lot of clients? I know you hate managing them, don't you?&lt;br&gt;
Follow my way to manage it easily, no need to use difficult logic and make you frustrated.&lt;/p&gt;

&lt;p&gt;All you need is nestjs, postgres. Yes, that's enough.&lt;br&gt;
We will use multi tenancy method with postgres multi schema. Maybe if it is described it will be like this&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwlptx3qep9fzi6k7lkb9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwlptx3qep9fzi6k7lkb9.png" alt="Multi Schema" width="800" height="579"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's say you have 100 clients, and each of them has 100 data items. If you use the old method of 1 table for all clients then your table contains 10,000 jumbled data. You need to think about database performance, api performance, think about the logic to filter, add indexes to certain fields.&lt;/p&gt;

&lt;p&gt;But if you use this method, you don't need to think about all that. &lt;/p&gt;

&lt;p&gt;"Hey, if only talking is easy", oh yes take it easy, I have prepared an example of a repo, you just need to clone it and modify it. &lt;a href="https://github.com/alfianriv/monorepo-nestjs-multi-tenancy-mikroorm"&gt;repo here&lt;/a&gt;.&lt;br&gt;
"Hey then how do I use it?", well I will explain it from the beginning here.&lt;/p&gt;

&lt;p&gt;Before that, I assume you are already proficient in using typescript. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You must clone the repo to your local. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This repo example uses the nestjs monorepo method, for those who don't know, maybe you can read the nestjs documentation.&lt;br&gt;
In this example repo has 2 services, namely identity and inventory&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identity Service is very important, to store your client's identity data (Identity Service only has crud Users).&lt;/li&gt;
&lt;li&gt;In addition, it is a service that will become multi tenancy in this example I created an Inventory Service (Inventory Service only has crud Items)&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Install depedency using npm yes &lt;code&gt;npm install&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create &lt;code&gt;.env&lt;/code&gt; file using &lt;code&gt;.env.example&lt;/code&gt; and fill it according to your database&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run the &lt;code&gt;npm run migrate&lt;/code&gt; command, this function is for migrating the User table in the public schema&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run the 2 services using the commands &lt;code&gt;npm run start:dev identity&lt;/code&gt; and &lt;code&gt;npm run start:dev inventory&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create one user using the endpoint &lt;code&gt;[POST]http://localhost:3000/users&lt;/code&gt;, save the created id for later.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To create a new schema automatically you can simply hit the &lt;code&gt;[GET]http://localhost:3000/users/migrate&lt;/code&gt; endpoint.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Now you check, there will definitely be a new schema, right?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To use the endpoint on the Inventory service, just use the &lt;code&gt;[GET]http://localhost:3001/items&lt;/code&gt; endpoint, but don't forget to use &lt;code&gt;x-client=id user&lt;/code&gt; in the headers with the value of the id created earlier.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can repeat step 6 onwards to create a new client schema.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There may be a few things to note to extend or modify this example repo.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;If you want to add new migrations you can simply run the command &lt;code&gt;npx mikro-orm migration:create --config ./apps/identity/mikro-orm.config.ts&lt;/code&gt; for service Identity and &lt;code&gt;npx mikro-orm migration:create --config ./apps/inventory/mikro-orm.config.ts&lt;/code&gt; for service Inventory&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Especially Inventory migrations you need to modify by following the first migration in the folder. Because the migration process on Inventory uses dyanmic schema and Mikroorm does not support it yet. So I modified it the way it is now.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I think that's enough. I think the rest you understand better. Maybe if there are questions and collaboration, you can contact me.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>postgres</category>
      <category>programming</category>
      <category>database</category>
    </item>
  </channel>
</rss>
