<?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: M.Hakim Amransyah</title>
    <description>The latest articles on Forem by M.Hakim Amransyah (@mhakimamransyah).</description>
    <link>https://forem.com/mhakimamransyah</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%2F1106891%2F51c4c515-fed5-403d-a92f-7dfaac712d1a.png</url>
      <title>Forem: M.Hakim Amransyah</title>
      <link>https://forem.com/mhakimamransyah</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/mhakimamransyah"/>
    <language>en</language>
    <item>
      <title>I am trying to merge all json data from paginated api responses with Go, this is how it looks like</title>
      <dc:creator>M.Hakim Amransyah</dc:creator>
      <pubDate>Thu, 31 Aug 2023 12:49:43 +0000</pubDate>
      <link>https://forem.com/mhakimamransyah/i-am-trying-to-merge-all-json-data-from-paginated-api-responses-with-go-this-is-how-it-looks-like-2c85</link>
      <guid>https://forem.com/mhakimamransyah/i-am-trying-to-merge-all-json-data-from-paginated-api-responses-with-go-this-is-how-it-looks-like-2c85</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Once upon a time, I need to consume list of users data from an api which uses json response. As I predicted earlier, the api provider creates a pagination responses when the consumer interacts with list. Unfortunately I need to consume all users which means I have to hit every possible pagination query parameter to fetch all users. &lt;/p&gt;

&lt;p&gt;here's the api endpoint&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.../api/v2/users?page=1&amp;amp;per_page=4&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;and here's the api response&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "meta": {
    "page": 1,
    "per_page": 5,
    "total_pages": 100
  },
  "data": [
    {
      "id": "ef5b4299-b8d0-40c2-8442-2fdc90e342ae",
      "firstname": "Cale",
      "lastname": "Schmidt",
      "email": "Liana8@gmail.com"
    },
    {
      "id": "c0328632-1f18-4cf6-ad53-17278ffcc7bb",
      "firstname": "Norval",
      "lastname": "Feest",
      "email": "Eleanora.Gleason55@gmail.com"
    },
    {
      "id": "0c467516-444f-4d01-b40d-82f7a646820a",
      "firstname": "Muriel",
      "lastname": "Berge",
      "email": "Clarissa_Simonis44@hotmail.com"
    },
    {
      "id": "2942a4b0-eac4-463c-87e3-6c7d0406cc81",
      "firstname": "Cletus",
      "lastname": "Considine",
      "email": "Casper.Kris77@gmail.com"
    },
    {
      "id": "2942a4b0-eac4-463c-87e3-6c7d0406cc81",
      "firstname": "Cletus",
      "lastname": "Considine",
      "email": "Casper.Kris77@gmail.com"
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First thing that comes to my mind when i face this problem is i go to first page and get last page information and then i will iterate to reach every possible page and merge every user from api response. To improve performance then I need to do it asynchronously for each page. As a result the user data I manage to merge is not sequential and I'm ok with that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;I developed a module which provide the functionality as i mention earlier. You can get it in this &lt;a href="https://github.com/Mhakimamransyah/go-pagination-aggregate"&gt;repository&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;

&lt;p&gt;Create a struct that representing json response which bind api response to a static data type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type Meta struct {
    Page    int `json:"page"`
    PerPage int `json:"per_page"`
    TotalPages int `json:"total_pages"`
}

type User struct {
    Uuid  string `json:"id" gorm:"column:id;primaryKey"`
    FirstName string `json:"firstname" gorm:"column:firstname"`
    LastName  string `json:"lastname" gorm:"column:lastname"`
    Email     string `json:"email" gorm:"column:email"`
}

type Response struct {
    Meta Meta   `json:"meta"`
    User []User `json:"data"`
}

func (resp *Response) GetBoundary() int {
    return resp.Meta.TotalPages
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In case you're asking about tag "gorm" , yes i will use it. &lt;br&gt;
Then i create module instance with some configurations like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type Meta struct {
    Page    int `json:"page"`
    PerPage int `json:"per_page"`
    TotalPages int `json:"total_pages"`
}

type User struct {
    Uuid  string `json:"id" gorm:"column:id;primaryKey"`
    FirstName string `json:"firstname" gorm:"column:firstname"`
    LastName  string `json:"lastname" gorm:"column:lastname"`
    Email     string `json:"email" gorm:"column:email"`
}

type Response struct {
    Meta Meta   `json:"meta"`
    User []User `json:"data"`
}

func (resp *Response) GetBoundary() int {
    return resp.Meta.TotalPages
}

func main() {

   var jsonPages Response

  pag, err := paginationaggregator.NewPaginationAggregator( 
    &amp;amp;paginationaggregator.PaginationAggregatorConfig{
      URL:"http://127.0.0.1:3000/api/v2/users?per_page=5&amp;amp;page=%d",
      Client: &amp;amp;http.Client{},
      JsonPage: &amp;amp;jsonPages,
      Concurrent: 5,
    },
  )

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

&lt;/div&gt;



&lt;p&gt;As you can see, the library provides &lt;code&gt;Concurrent&lt;/code&gt; configuration. This configuration means if you need to make 100 requests for each page then there will be 20 batch asynchronous requests or each batch will make 5 asynchronous requests until all page fetched. I think it will prevent api server considers all these requests as DDOS attacks and prevents requests from hitting rate limiters.&lt;/p&gt;

&lt;h3&gt;
  
  
  Insert user to database
&lt;/h3&gt;

&lt;p&gt;After successfully retrieving all user then I need to insert it into database only if user email does not exist. Rather than waiting for all user successfully retrieved, the library provides a function so we can process it in each batch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type Meta struct {
   Page       int `json:"page"`
   PerPage    int `json:"per_page"`
   TotalPages int `json:"total_pages"`
}

type User struct {
   Uuid      string `json:"id" gorm:"column:id;primaryKey"`
   FirstName string `json:"firstname" gorm:"column:firstname"`
   LastName  string `json:"lastname" gorm:"column:lastname"`
   Email     string `json:"email" gorm:"column:email"`
}

type Response struct {
   Meta Meta   `json:"meta"`
   User []User `json:"data"`
}

func (resp *Response) GetBoundary() int {
   return resp.Meta.TotalPages
}

var Mysql *gorm.DB

func main() {

  var jsonPages Response

  Mysql = connectMysql()
  defer func() {
     sql, _ := Mysql.DB()
     sql.Close()
  }()

  pag, err := paginationaggregator.NewPaginationAggregator(
   &amp;amp;paginationaggregator.PaginationAggregatorConfig{
      URL:"http://127.0.0.1:3000/api/v2/users?per_page=5&amp;amp;page=%d",
      Client: &amp;amp;http.Client{},
      JsonPage: &amp;amp;jsonPages,
      Concurrent: 5,
      ConcurrentBatch: processingBatch,
     },
  )

  if err != nil {
     fmt.Println(err)
     return
  }

  res, err := pag.Get()

  if err != nil {
     fmt.Println(err)
     return
  }

  for _, val := range res {
     fmt.Println(val.Response.Data)
  }
}

// select and insert user for every batch
func processingBatch(
    batchResult []paginationaggregator.HttpInteraction
) error {

   var newUsers []User

   for _, val := range batchResult {

      resp := Response{}

      if err := json.Unmarshal([]byte(val.Response.Data), 
                   &amp;amp;resp); err != nil {
         continue
      }

      for _, user := range resp.User {

       userTmp := User{}

           // check existing user by email
           if err := Mysql.Where("email = ?", 
                user.Email).First(&amp;amp;userTmp).Error; err != nil {

              if errors.Is(err, gorm.ErrRecordNotFound) {
                       // users not exists so append it
                  newUsers = append(newUsers, user)
              }

             continue

          }
       }
    }

    if err := Mysql.Create(&amp;amp;newUsers).Error; err != nil {
         return err
    }

    return nil
}

func connectMysql() *gorm.DB {

    dsn := "root:yourpassword@tcp(127.0.0.1:3306)/peoples"

    DB, err := gorm.Open(mysql.Open(dsn), &amp;amp;gorm.Config{
        Logger: logger.Default.LogMode(logger.Silent),
    })

    if err != nil {
    panic(err)
    }

    return DB
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Reference
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/Mhakimamransyah/go-pagination-aggregate"&gt;https://github.com/Mhakimamransyah/go-pagination-aggregate&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>go</category>
      <category>restapi</category>
      <category>json</category>
    </item>
    <item>
      <title>Di Pandu oleh Iterator Pattern</title>
      <dc:creator>M.Hakim Amransyah</dc:creator>
      <pubDate>Fri, 07 Jul 2023 02:02:01 +0000</pubDate>
      <link>https://forem.com/mhakimamransyah/di-pandu-oleh-iterator-pattern-27g8</link>
      <guid>https://forem.com/mhakimamransyah/di-pandu-oleh-iterator-pattern-27g8</guid>
      <description>&lt;p&gt;Artikel Ini adalah lanjutan pembahasan implementasi &lt;em&gt;design-pattern&lt;/em&gt; yang sebelumnya dibahas pada &lt;a href="https://dev.to/mhakimamransyah/bersolek-ria-dengan-decorator-pattern-2j7h"&gt;Bersolek Ria dengan Decorator-pattern&lt;/a&gt;. Domain permasalah dan &lt;em&gt;sources code&lt;/em&gt; yang digunakan merupakan lanjutan dari pembahasan sebelumnya.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prolog
&lt;/h2&gt;

&lt;p&gt;Perusahaan tempat anda bekerja ternyata memiliki segudang ide dan rencana terkait dengan aplikasi yang anda kelola. Hari ini anda, atasan anda dan seorang &lt;em&gt;product owner&lt;/em&gt; melakukan &lt;em&gt;virtual meeting&lt;/em&gt; terkait &lt;em&gt;enhancement&lt;/em&gt; dari aplikasi tersebut. Diskusi berakhir dengan sebuah tugas yang di bebankan kepada anda melalui atasan anda.&lt;/p&gt;

&lt;p&gt;Anda diminta untuk menambahkan fungsi untuk memfilter setiap pegawai yang akan dikirimkan notifikasi peringatan ke &lt;em&gt;sources code&lt;/em&gt; yang ada. Saat ini aplikasi anda bersifat "buta" dengan mengirimkan notifikasi peringatan kepada seluruh pegawai di setiap level tanpa memperhatikan apakah status pegawai tersebut masih aktif atau tidak.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open for extension Closed for modification
&lt;/h2&gt;

&lt;p&gt;Atasan anda meminta agar filter yang dibuat bisa digunakan dimanapun ketika ada fitur yang membutuhkan. Filter tersebut juga diharapkan nantinya tidak perlu dimodifikasi apabila ada kebutuhan untuk memfilter pegawai dengan cara yang berbeda. Atasan anda kemudian menyebut sebuah kata yang bisa mengubah hidup anda sepenuhnya, dia berkata &lt;/p&gt;

&lt;p&gt;"&lt;strong&gt;&lt;em&gt;Coba pelajari iterator pattern&lt;/em&gt;&lt;/strong&gt;"&lt;/p&gt;

&lt;h2&gt;
  
  
  Iterator
&lt;/h2&gt;

&lt;p&gt;Setelah mendapatkan petunjuk dari atasan anda, anda memutuskan untuk menggunakan &lt;em&gt;iterator pattern&lt;/em&gt; pada kasus ini. Anda berencana untuk membuat sebuah objek yang hanya bertanggung jawab untuk memfilter status aktif dari pegawai. Anda juga tahu bahwa dengan menggunakan &lt;em&gt;pattern&lt;/em&gt; ini &lt;em&gt;iterator&lt;/em&gt; lain bisa dengan mudah ditambahkan apabila nanti dibutuhkan.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Oe1mEnll--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8ku9qya9uqqdpln2svyk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Oe1mEnll--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8ku9qya9uqqdpln2svyk.png" alt="Image description" width="578" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;iterator.go&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;type Iterator interface {
    GetNext() Employee
    HasMore() bool
}

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;active_employee_iterator.go&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;type ActiveEmployeeIterator struct {
    Employees       []entity.Employee
    CurrentPosition int
    Status          bool
}

func (iter *ActiveEmployeeIterator) GetNext() entity.Employee {

    if !iter.HasMore() {
        return nil
    }

    employee := iter.Employees[iter.CurrentPosition]

    iter.CurrentPosition++

    if employee.GetStatus() {
        return employee
    }

    return iter.GetNext()

}
func (iter *ActiveEmployeeIterator) HasMore() bool {

    if iter.CurrentPosition &amp;gt;= len(iter.Employees) {
        return false
    }

    employee := iter.Employees[iter.CurrentPosition]

    if employee.GetStatus() {
        return iter.CurrentPosition &amp;lt; len(iter.Employees)
    }

    iter.CurrentPosition++

    return iter.HasMore()

}

func NewActiveEmployeeIterator(employees []entity.Employee, status bool) *ActiveEmployeeIterator {
    return &amp;amp;ActiveEmployeeIterator{Employees: employees, Status: status}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Panggil iterator pada main program sehingga;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;main.go&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;// iterate only for active employee
    employeesIter := iterator.NewActiveEmployeeIterator(employees, true)

    for employeesIter.HasMore() {

        employee := employeesIter.GetNext()

        // accept performance notification visitor
        employee.Accept(performanceNotification)

        // accept bonus income visitor
        employee.Accept(bonusIncome)

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Epilog
&lt;/h2&gt;

&lt;p&gt;Seluruh &lt;em&gt;sources code&lt;/em&gt; yang berkaitan dengan artikel ini dapat dilihat pada &lt;em&gt;repository&lt;/em&gt; berikut &lt;a href="https://github.com/Mhakimamransyah/practice-design-pattern"&gt;https://github.com/Mhakimamransyah/practice-design-pattern&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Referensi Bacaan&lt;br&gt;
&lt;a href="https://refactoring.guru/design-patterns/iterator"&gt;https://refactoring.guru/design-patterns/iterator&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Author&lt;br&gt;
&lt;a href="https://github.com/Mhakimamransyah"&gt;https://github.com/Mhakimamransyah&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.linkedin.com/in/hakim-amr/"&gt;https://www.linkedin.com/in/hakim-amr/&lt;/a&gt;&lt;br&gt;
mailto: &lt;a href="mailto:m.hakim.amransyah.hakim@gmail.com"&gt;m.hakim.amransyah.hakim@gmail.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>indonesia</category>
      <category>go</category>
      <category>designpatterns</category>
    </item>
    <item>
      <title>Bersolek Ria dengan Decorator Pattern</title>
      <dc:creator>M.Hakim Amransyah</dc:creator>
      <pubDate>Sat, 01 Jul 2023 08:31:29 +0000</pubDate>
      <link>https://forem.com/mhakimamransyah/bersolek-ria-dengan-decorator-pattern-2j7h</link>
      <guid>https://forem.com/mhakimamransyah/bersolek-ria-dengan-decorator-pattern-2j7h</guid>
      <description>&lt;p&gt;Artikel Ini adalah lanjutan pembahasan implementasi &lt;em&gt;design-pattern&lt;/em&gt; yang sebelumnya dibahas pada &lt;em&gt;&lt;a href="https://dev.to/mhakimamransyah/lebih-dekat-dengan-visitor-pattern-2747"&gt;lebih dekat dengan visitor-pattern&lt;/a&gt;&lt;/em&gt;. Domain permasalah dan &lt;em&gt;sources code&lt;/em&gt; yang digunakan merupakan lanjutan dari pembahasan sebelumnya.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prolog
&lt;/h2&gt;

&lt;p&gt;Pada saat &lt;em&gt;daily standup&lt;/em&gt; kemarin anda mendapatkan arahan dari atasan anda untuk melakukan &lt;em&gt;enhance&lt;/em&gt; pada fitur pengiriman notifikasi peringatan yang telah dibuat. Sebelumnya anda hanya menggunakan fungsi sederhana untuk mengirimkan notifikasi peringatan ke seluruh level pegawai. Atasan anda meminta agar &lt;em&gt;sources code&lt;/em&gt; yang ada mampu mengakomodasi untuk mengirimkan notifikasi menggunakan beberapa media yang berbeda-beda. Adapun ketentuan yang diminta oleh atasan anda adalah;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Seluruh level pegawai akan menerima notifikasi melalui &lt;em&gt;push notification&lt;/em&gt; pada aplikasi &lt;em&gt;mobile&lt;/em&gt; perusahaan.&lt;/li&gt;
&lt;li&gt;Selain &lt;em&gt;push notification&lt;/em&gt;, &lt;strong&gt;supervisor&lt;/strong&gt; dapat menerima notifikasi melalui email.&lt;/li&gt;
&lt;li&gt;Selain &lt;em&gt;push notification&lt;/em&gt;, &lt;strong&gt;manajer&lt;/strong&gt; dapat menerima notifikasi melalui email &amp;amp; pesan &lt;em&gt;whatsapp&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Secara umum saat ini aplikasi anda memiliki relasi kelas sebagai berikut; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LDfPjAgB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/580jhan8l8ltqob4qelg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LDfPjAgB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/580jhan8l8ltqob4qelg.png" alt="Image description" width="792" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tanggung jawab pengiriman notifikasi peringatan saat ini diemban oleh objek &lt;em&gt;PerformanceWarningVisitor&lt;/em&gt; yang merupakan implementasi dari &lt;em&gt;visitor-pattern&lt;/em&gt; (dijelaskan pada &lt;em&gt;&lt;a href="https://dev.to/mhakimamransyah/lebih-dekat-dengan-visitor-pattern-2747"&gt;lebih dekat dengan visitor-pattern&lt;/a&gt;&lt;/em&gt; ).&lt;/p&gt;

&lt;h2&gt;
  
  
  Mendekor ulang objek
&lt;/h2&gt;

&lt;p&gt;Anda tahu bahwa saat ini notifikasi peringatan hanya dikirimkan melalui &lt;em&gt;push notifications&lt;/em&gt;. Sesuai dengan ketentuan yang diberikan, anda harus menambahkan media notifikasi lainnya tanpa menghapus atau menghilangkan &lt;em&gt;default&lt;/em&gt; notifikasi yang saat ini digunakan.&lt;/p&gt;

&lt;p&gt;Anda sadar bahwa membuat objek yang berdiri sendiri bukanlah ide yang bagus karena (sekali lagi) setiap jenis notifikasi akan berakumulasi dengan jenis notifikasi lain. Sederhananya ketentuan tersebut dapat dijelaskan seperti berikut&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Staf&lt;/strong&gt;       -&amp;gt; &lt;em&gt;Push Notification&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;Supervisor&lt;/strong&gt; -&amp;gt; &lt;em&gt;Push Notification&lt;/em&gt; | Email&lt;br&gt;
&lt;strong&gt;Manajer&lt;/strong&gt; -&amp;gt; &lt;em&gt;Push Notification&lt;/em&gt; | Email | &lt;em&gt;WhatsApp Number&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Decorator Pattern
&lt;/h2&gt;

&lt;p&gt;Setelah gugling dan bertapa cukup lama, anda akhirnya memutuskan untuk menggunakan &lt;em&gt;decorator-pattern&lt;/em&gt; untuk menangani kasus ini. Anda akan membuat sebuah &lt;em&gt;decorator&lt;/em&gt; yang akan mendekorasi ulang fungsi &lt;em&gt;push-notifications&lt;/em&gt; sehingga memiliki fungsi notifikasi dengan media yang berbeda-beda.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kT5JbEgX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1oddvs6z0agoks7x3mzr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kT5JbEgX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1oddvs6z0agoks7x3mzr.png" alt="Image description" width="312" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Anda membuat sebuah kelas &lt;em&gt;factory&lt;/em&gt; yang bertugas untuk meng-&lt;em&gt;assemble&lt;/em&gt; berbagai macam &lt;em&gt;decorator&lt;/em&gt; sesuai dengan level pegawai yang ada. Singkatnya implementasi kelas tersebut adalah sebagai berikut;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;notification_factory.go&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;type NotificatorContract interface {
    StaffNotifier() entity.Notifier
    SupervisorNotifier() entity.Notifier
    ManagerNotifier() entity.Notifier
}

type Notificator struct{}

func (notifier *Notificator) StaffNotifier() entity.Notifier {
    return decorator.NewPushNotificationDecorator()
}

func (notifier *Notificator) SupervisorNotifier() entity.Notifier {
    pushNotification := decorator.NewPushNotificationDecorator()
    emailNotification := decorator.NewEmailDecorator(pushNotification)
    return emailNotification
}

func (notifier *Notificator) ManagerNotifier() entity.Notifier {
    pushNotification := decorator.NewPushNotificationDecorator()
    emailNotification := decorator.NewEmailDecorator(pushNotification)
    whatsAppNotification := decorator.NewWhatsAppDecorator(emailNotification)
    return whatsAppNotification
}

func NewNotificator() *Notificator {
    return &amp;amp;Notificator{}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Factory&lt;/em&gt; tersebut nantinya akan dimanfaatkan oleh objek &lt;em&gt;PerformanceWarningVisitor&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;performance_warning_visitor.go&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;type PerformanceWarningVisitor struct {
    Notif factory.NotificatorContract
}

func (visitor PerformanceWarningVisitor) VisitStaff(staff *entities.Staff) (interface{}, error) {

    var warningType string

    notification := visitor.Notif.StaffNotifier()

    if staff.PerformancePercentage &amp;lt; 30 {
        warningType = STERN_WARNING
    }

    if staff.PerformancePercentage &amp;gt;= 30 &amp;amp;&amp;amp; staff.PerformancePercentage &amp;lt; 67 {
        warningType = WARNING
    }

    notification.SendMessage(
        staff,
        fmt.Sprintf("Notification %s send to staff : %s \n", warningType, staff.Name),
    )

    return warningType, nil
}

func (visitor PerformanceWarningVisitor) VisitSupervisor(spv *entities.Supervisor) (interface{}, error) {

    var warningType string

    durationInYear := (time.Now()).Sub(spv.JoinDate).Hours() / 8760
    notification := visitor.Notif.SupervisorNotifier()

    if spv.PerformancePercentage &amp;lt; 30 {
        warningType = STERN_WARNING
    }

    if durationInYear &amp;gt;= 10 &amp;amp;&amp;amp; (spv.PerformancePercentage &amp;gt;= 30 &amp;amp;&amp;amp; spv.PerformancePercentage &amp;lt; 60) {
        warningType = WARNING
    }

    if durationInYear &amp;lt; 10 &amp;amp;&amp;amp; (spv.PerformancePercentage &amp;gt;= 30 &amp;amp;&amp;amp; spv.PerformancePercentage &amp;lt; 70) {
        warningType = WARNING
    }

    notification.SendMessage(
        spv,
        fmt.Sprintf("Notification %s send to supervisor : %s \n", warningType, spv.Name),
    )

    return warningType, nil
}

func (visitor PerformanceWarningVisitor) VisitManager(manager *entities.Manager) (interface{}, error) {

    var warningType string

    Age := (time.Now()).Sub(manager.BirthDate).Hours() / 8760
    notification := visitor.Notif.ManagerNotifier()

    if manager.PerformancePercentage &amp;lt; 40 {
        warningType = STERN_WARNING
    }

    if Age &amp;gt;= 40 &amp;amp;&amp;amp; (manager.PerformancePercentage &amp;gt;= 40 &amp;amp;&amp;amp; manager.PerformancePercentage &amp;lt; 60) {
        warningType = WARNING
    }

    if Age &amp;lt; 40 &amp;amp;&amp;amp; (manager.PerformancePercentage &amp;gt;= 40 &amp;amp;&amp;amp; manager.PerformancePercentage &amp;lt; 70) {
        warningType = WARNING
    }

    notification.SendMessage(
        manager,
        fmt.Sprintf("Notification %s send to manager : %s \n", warningType, manager.Name),
    )

    return warningType, nil
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Epilog
&lt;/h2&gt;

&lt;p&gt;Seluruh sources code yang berkaitan dengan artikel ini dapat dilihat pada repository berikut &lt;a href="https://github.com/Mhakimamransyah/practice-design-pattern"&gt;https://github.com/Mhakimamransyah/practice-design-pattern&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referensi Bacaan&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://refactoring.guru/design-patterns/decorator"&gt;https://refactoring.guru/design-patterns/decorator&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.youtube.com/watch?v=metYIcjQLls"&gt;https://www.youtube.com/watch?v=metYIcjQLls&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Author&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://github.com/Mhakimamransyah"&gt;https://github.com/Mhakimamransyah&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.linkedin.com/in/hakim-amr/"&gt;https://www.linkedin.com/in/hakim-amr/&lt;/a&gt;&lt;br&gt;
mailto: &lt;a href="mailto:m.hakim.amransyah.hakim@gmail.com"&gt;m.hakim.amransyah.hakim@gmail.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>indonesia</category>
      <category>designpatterns</category>
      <category>go</category>
    </item>
    <item>
      <title>Lebih Dekat dengan Visitor Pattern</title>
      <dc:creator>M.Hakim Amransyah</dc:creator>
      <pubDate>Wed, 28 Jun 2023 08:21:18 +0000</pubDate>
      <link>https://forem.com/mhakimamransyah/lebih-dekat-dengan-visitor-pattern-2747</link>
      <guid>https://forem.com/mhakimamransyah/lebih-dekat-dengan-visitor-pattern-2747</guid>
      <description>&lt;h3&gt;
  
  
  Prolog
&lt;/h3&gt;

&lt;p&gt;Alkisah anda sebagai seorang &lt;em&gt;backend engineer&lt;/em&gt; dihadapkan kepada sebuah tugas untuk menambahkan fitur notifikasi peringatan kepada seluruh level pegawai dengan ketentuan yang bervariasi. Ketentuan yang dimaksud antara lain;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Staf

&lt;ul&gt;
&lt;li&gt;Staf yang memiliki memiliki KPI dibawah 30% akan menerima 
notifikasi &lt;strong&gt;"peringatan-keras"&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Staf yang memiliki KPI dibawah 67% dan diatas 30% akan 
menerima notifikasi &lt;strong&gt;"peringatan"&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Supervisor

&lt;ul&gt;
&lt;li&gt;Supervisor yang memiliki memiliki KPI dibawah 30% akan 
menerima notifikasi &lt;strong&gt;"peringatan-keras"&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Supervisor yang memiliki masa kerja di atas 10 tahun dan 
memiliki KPI diatas 30% dan dibawah 60% akan menerima 
notifikasi &lt;strong&gt;"peringatan"&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Supervisor memiliki masa kerja di bawah 10 tahun dan memiliki 
KPI diatas 30% dan dibawah 70% akan menerima notifikasi 
&lt;strong&gt;"peringatan"&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Manajer

&lt;ul&gt;
&lt;li&gt;Manajer yang memiliki memiliki KPI dibawah 40% akan menerima 
notifikasi &lt;strong&gt;"peringatan-keras"&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Manajer yang memiliki usia di atas 40 tahun dan 
memiliki KPI diatas 40% dan dibawah 60% akan menerima 
notifikasi &lt;strong&gt;"peringatan"&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Manajer yang memiliki usia di bawah 40 tahun dan 
memiliki KPI diatas 40% dan dibawah 70% akan menerima 
notifikasi &lt;strong&gt;"peringatan"&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Aplikasi anda saat ini dibuat menggunakan bahasa pemrograman &lt;em&gt;go&lt;/em&gt; dan hanya memiliki 4 kelas yang membedakan setiap level pegawai yang ada. Tugas anda adalah menambahkan fungsionalitas  sesuai dengan ketentuan tersebut ke &lt;em&gt;sources code&lt;/em&gt; yang ada.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gpzKBOWg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/orfvbwez3w35gov9x9r2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gpzKBOWg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/orfvbwez3w35gov9x9r2.png" alt="Image description" width="800" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Ide yang Tertolak
&lt;/h3&gt;

&lt;p&gt;Anda berdiskusi dengan atasan anda dan menyampaikan ide bahwa anda akan menambahkan sebuah fungsi &lt;em&gt;sendWarningNotification&lt;/em&gt; pada objek Staf, Supervisor dan Manager. Atasan anda mengerenyutkan dahi dan bertanya kepada anda.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Apakah pegawai di perusahaan kita sekarang berperan sebagai seorang pemberi peringatan ?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Diskusi berlanjut dan atasan anda menolak ide anda karena dianggap menambahkan sifat "alien" ke ketiga objek tersebut. Atasan anda juga men-&lt;em&gt;changelle&lt;/em&gt; bagaimana ide tersebut nantinya akan mampu mengakomodasi apabila ada penambahan &lt;em&gt;behaviour&lt;/em&gt; lagi terhadap ketiga objek ini.&lt;/p&gt;

&lt;h3&gt;
  
  
  Visitor Pattern
&lt;/h3&gt;

&lt;p&gt;Pada akhirnya anda dan atasan memutuskan untuk menggunakan &lt;em&gt;visitor pattern&lt;/em&gt; untuk menangani kasus ini.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hjYpkv8U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rjhtxs9x99ed0nar9su2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hjYpkv8U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rjhtxs9x99ed0nar9su2.png" alt="Image description" width="800" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Seluruh logika pengiriman notifikasi peringatan akan menjadi tanggung jawab dari objek &lt;em&gt;PerformanceWarningVisitor&lt;/em&gt;. Setiap objek turunan &lt;em&gt;employee&lt;/em&gt;(Staff, Supervisor dan Manager) tidak perlu tahu definisi dan implementasi pengiriman notifikasi peringatan. Objek Staff, Supervisor dan Manager hanya perlu memanggil method &lt;em&gt;Accept&lt;/em&gt; dengan paramater &lt;em&gt;PerformanceWarningVisitor&lt;/em&gt; untuk mengirimkan notifikasi peringatan kepada seluruh level pegawai.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;main.go&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;func main() {

    employees := make([]entities.Employee, 0)

    performanceNotification := visitor.PerformanceWarningVisitor{}

    employees = append(employees,
        entities.Staff{
            Id:                    12343,
            Name:                  "Pedro Pacquita",
            JoinDate:              time.Date(2011, time.February, 10, 0, 0, 0, 0, time.UTC),
            BirthDate:             time.Date(1990, time.January, 07, 0, 0, 0, 0, time.UTC),
            PerformancePercentage: 47.5,
        },
        entities.Supervisor{
            Id:                    12342,
            Name:                  "Omar Maulo Atarez",
            JoinDate:              time.Date(2005, time.December, 15, 0, 0, 0, 0, time.UTC),
            BirthDate:             time.Date(1987, time.December, 02, 0, 0, 0, 0, time.UTC),
            PerformancePercentage: 75,
        },
        entities.Manager{
            Id:                    12312,
            Name:                  "Zakaria Owele",
            JoinDate:              time.Date(1993, time.December, 04, 0, 0, 0, 0, time.UTC),
            BirthDate:             time.Date(1971, time.July, 15, 0, 0, 0, 0, time.UTC),
            PerformancePercentage: 50,
        },
    )

    for _, employee := range employees {

        // send performance notification
        employee.Accept(performanceNotification)

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;staff.go&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;type Staff struct {
    Id                    int
    Name                  string
    JoinDate              time.Time
    BirthDate             time.Time
    PerformancePercentage float32
}

func (staff Staff) Accept(v Visitor) {
    v.VisitStaff(&amp;amp;staff)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;supervisor.go&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;type Supervisor struct {
    Id                    int
    Name                  string
    JoinDate              time.Time
    BirthDate             time.Time
    PerformancePercentage float32
}

func (spv Supervisor) Accept(visitor Visitor) {
    visitor.VisitSupervisor(&amp;amp;spv)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;manager.go&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;type Manager struct {
    Id                    int
    Name                  string
    JoinDate              time.Time
    BirthDate             time.Time
    PerformancePercentage float32
}

func (manager Manager) Accept(visitor Visitor) {
    visitor.VisitManager(&amp;amp;manager)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;performance_warning_visitor.go&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;type PerformanceWarningVisitor struct{}

func (visitor PerformanceWarningVisitor) VisitStaff(staff *entities.Staff) (interface{}, error) {

    var warningType string

    if staff.PerformancePercentage &amp;lt; 30 {
        warningType = STERN_WARNING
    }

    if staff.PerformancePercentage &amp;gt;= 30 &amp;amp;&amp;amp; staff.PerformancePercentage &amp;lt; 67 {
        warningType = WARNING
    }

    if warningType != "" {
        fmt.Printf("Notification %s send to staff : %s \n", warningType, staff.Name)
    }

    return warningType, nil
}

func (visitor PerformanceWarningVisitor) VisitSupervisor(spv *entities.Supervisor) (interface{}, error) {

    var warningType string
    durationInYear := (time.Now()).Sub(spv.JoinDate).Hours() / 8760

    if spv.PerformancePercentage &amp;lt; 30 {
        warningType = STERN_WARNING
    }

    if durationInYear &amp;gt;= 10 &amp;amp;&amp;amp; (spv.PerformancePercentage &amp;gt;= 30 &amp;amp;&amp;amp; spv.PerformancePercentage &amp;lt; 60) {
        warningType = WARNING
    }

    if durationInYear &amp;lt; 10 &amp;amp;&amp;amp; (spv.PerformancePercentage &amp;gt;= 30 &amp;amp;&amp;amp; spv.PerformancePercentage &amp;lt; 70) {
        warningType = WARNING
    }

    if warningType != "" {
        fmt.Printf("Notification %s send to supervisor : %s \n", warningType, spv.Name)
    }

    return warningType, nil
}

func (visitor PerformanceWarningVisitor) VisitManager(manager *entities.Manager) (interface{}, error) {

    var warningType string
    Age := (time.Now()).Sub(manager.BirthDate).Hours() / 8760

    if manager.PerformancePercentage &amp;lt; 40 {
        warningType = STERN_WARNING
    }

    if Age &amp;gt;= 40 &amp;amp;&amp;amp; (manager.PerformancePercentage &amp;gt;= 40 &amp;amp;&amp;amp; manager.PerformancePercentage &amp;lt; 60) {
        warningType = WARNING
    }

    if Age &amp;lt; 40 &amp;amp;&amp;amp; (manager.PerformancePercentage &amp;gt;= 40 &amp;amp;&amp;amp; manager.PerformancePercentage &amp;lt; 70) {
        warningType = WARNING
    }

    if warningType != "" {
        fmt.Printf("Notification %s send to manager : %s \n", warningType, manager.Name)
    }

    return warningType, nil
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Perubahan adalah Kepastian
&lt;/h3&gt;

&lt;p&gt;Seiring berjalannya waktu prediksi atasan anda akan adanya penambahan fitur yang melibatkan ketiga objek &lt;em&gt;employee&lt;/em&gt; ini ternyata menjadi kenyataan.&lt;/p&gt;

&lt;p&gt;Anda kembali diminta untuk menambahkan fitur untuk menghitung bonus pendapatan tambahan dari masing-masing level &lt;em&gt;employee&lt;/em&gt; dengan ketentuan sebagai berikut ;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Staf

&lt;ul&gt;
&lt;li&gt;Setiap staf akan menerima bonus pendapatan tambahan sebesar 
1000000 apabila mempeeoleh KPI di atas 87%&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Supervisor

&lt;ul&gt;
&lt;li&gt;Setiap supervisor akan menerima bonus pendapatan tambahan 
sebesar 2000000 apabila mempeeoleh KPI di atas 85%&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Manajer

&lt;ul&gt;
&lt;li&gt;Setiap manajer akan menerima bonus pendapatan tambahan sebesar 
5000000 apabila mempeeoleh KPI di atas 80%&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Anda dan atasan tahu bahwa &lt;em&gt;sources code&lt;/em&gt; yang ada bisa di &lt;em&gt;expand&lt;/em&gt; dengan mengikuti pola &lt;em&gt;visitor pattern&lt;/em&gt; yang sudah ada.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NKyveHIy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jk5top2t4w4wn7kvlt8u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NKyveHIy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jk5top2t4w4wn7kvlt8u.png" alt="Image description" width="800" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Anda hanya perlu memanggil kembali method &lt;em&gt;Accept&lt;/em&gt; akan tetapi kali ini gunakan &lt;em&gt;BonusIncomeVisitor&lt;/em&gt; sebagai parameter nya&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for _, employee := range employees {

  // send performance notification
  employee.Accept(performanceNotification)

  // calculate bonus income
  employee.Accept(bonusIncome)

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Epilog
&lt;/h3&gt;

&lt;p&gt;Seluruh &lt;em&gt;sources code&lt;/em&gt; yang berkaitan dengan artikel ini dapat dilihat pada repository berikut &lt;a href="https://github.com/Mhakimamransyah/practice-design-pattern"&gt;https://github.com/Mhakimamransyah/practice-design-pattern&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bacaan Lanjutan&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/mhakimamransyah/bersolek-ria-dengan-decorator-pattern-2j7h"&gt;Decorator Pattern&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Referensi Bacaan&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://refactoring.guru/design-patterns/visitor"&gt;https://refactoring.guru/design-patterns/visitor&lt;/a&gt;&lt;br&gt;
&lt;a href="https://refactoring.guru/design-patterns/visitor/go/example"&gt;https://refactoring.guru/design-patterns/visitor/go/example&lt;/a&gt;&lt;br&gt;
&lt;a href="https://refactoring.guru/design-patterns/visitor-double-dispatch"&gt;https://refactoring.guru/design-patterns/visitor-double-dispatch&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Author&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://github.com/Mhakimamransyah"&gt;https://github.com/Mhakimamransyah&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.linkedin.com/in/hakim-amr/"&gt;https://www.linkedin.com/in/hakim-amr/&lt;/a&gt;&lt;br&gt;
mailto: &lt;a href="mailto:m.hakim.amransyah.hakim@gmail.com"&gt;m.hakim.amransyah.hakim@gmail.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>indonesia</category>
      <category>go</category>
      <category>designpatterns</category>
    </item>
    <item>
      <title>An Implementation of Microservice Messaging Using RabbitMQ and PHP</title>
      <dc:creator>M.Hakim Amransyah</dc:creator>
      <pubDate>Fri, 23 Jun 2023 07:28:15 +0000</pubDate>
      <link>https://forem.com/mhakimamransyah/an-implementation-of-microservice-messaging-using-rabbitmq-and-php-j8i</link>
      <guid>https://forem.com/mhakimamransyah/an-implementation-of-microservice-messaging-using-rabbitmq-and-php-j8i</guid>
      <description>&lt;p&gt;There are various methods that can be used to establish communication between systems. In the &lt;a href="https://www.amazon.com/dp/0321200683/ref=as_sl_pc_tf_til?tag=enterpriseint-20&amp;amp;linkCode=w00&amp;amp;linkId=&amp;amp;creativeASIN=0321200683" rel="noopener noreferrer"&gt;Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions&lt;/a&gt; it is stated that there are 65 ways that the author has successfully documented how between systems can communicate (read: &lt;a href="https://www.enterpriseintegrationpatterns.com/patterns/messaging/toc.html" rel="noopener noreferrer"&gt;https://www.enterpriseintegrationpatterns.com/patterns/messaging/toc.html&lt;/a&gt;) .&lt;/p&gt;

&lt;p&gt;This article will contain a simple implementation explanation in building communication between systems (services) using RabbitMQ where each services is created using the PHP programming language.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Case study&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once upon a time, in an company information technology ecosystem, there were 3 services consisting of payment-service, logistics-service and notification-service. These three services are able to communicate with each other in 2 ways, namely using API calls and messaging. Specifically for messaging, these three services use rabbitMQ as a message broker.&lt;/p&gt;

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

&lt;p&gt;In this case study, payment-services acts as a message sender (publisher) to the other two services every time there is a verified payment by carrying payment information made by the user.&lt;/p&gt;

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

&lt;p&gt;The other two services will act as consumers and payment-services as publishers and do not need to know what actions the two services will take.&lt;/p&gt;

&lt;p&gt;On the other hand, logistics-services besides only acting as consumers when interacting with payment-services can also send messages to notification-services every time a logistics package has reached a certain checkpoint before being received by the user.&lt;/p&gt;

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

&lt;p&gt;Simply put, it can be concluded that;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;payment-service acts as a publisher,&lt;/li&gt;
&lt;li&gt;notification-service acts as a consumer and&lt;/li&gt;
&lt;li&gt;logistic-service acts as a consumer as well as a publisher.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Technology Stack&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://hub.docker.com/_/rabbitmq" rel="noopener noreferrer"&gt;RabbitMQ Management Image&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hub.docker.com/_/php" rel="noopener noreferrer"&gt;PHP:7.4-cli Image&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://framework-x.org/" rel="noopener noreferrer"&gt;PHP Framework-X&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/php-amqplib/php-amqplib" rel="noopener noreferrer"&gt;PHP library php-amqplib&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jakubkulhan/bunny" rel="noopener noreferrer"&gt;PHP library bunny&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;PHP extension sockets &amp;amp; amqp&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Configure RabbitMQ&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;RabbitMQ will act as a message broker whenever the service needs to interact other than using an Api Call. We'll set up a few things about rabbitmq so that each service and rabbitmq can communicate properly.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Exchanger&lt;/em&gt;&lt;br&gt;
The exchanger can be thought of as a message distributor to every available queue. Simply put, each service does not need to have its own "logic" for routing where messages should be sent. Each service simply communicates with this exchanger. Each service will have its own exchanger which will be used as a message distributor.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Queue&lt;/em&gt;&lt;br&gt;
Queue is a collection of data that will be consumed directly by the consumer. Incoming data is data distributed by related exchangers. Each service that acts as a consumer must later "listen" to the queue related to the features that exist on it.&lt;/p&gt;

&lt;p&gt;The description of the configuration that will be implemented is as follows;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Testing Communication&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;All source code related to this article can be downloaded in the repository &lt;a href="https://github.com/Mhakimamransyah/php-long-connection-messaging" rel="noopener noreferrer"&gt;https://github.com/Mhakimamransyah/php-long-connection-messaging&lt;/a&gt; .&lt;/p&gt;

&lt;p&gt;To test the communication related to the approval-payment scenario, we can hit the approval end-point available in the payment-service.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

curl --location 'http://127.0.0.1:8001/api/v1/approval' \
--form 'id="12345"' \
--form 'type="VIRTUAL-ACCOUNT"' \
--form 'price="400000"' \
--form 'approved-by="system"' \
--form 'bank="BCA"'


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

&lt;/div&gt;

&lt;p&gt;To see if the payment information has been successfully received by the other two services, we can enter the container of each service and see the response via the logs.&lt;/p&gt;

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

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

&lt;p&gt;To test communication via logistics-services we can hit end-point packages.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

curl --location --request PUT 'http://127.0.0.1:8003/api/v1/packages' \
--form 'tracking-number="12345"' \
--form 'status="delivering"' \
--form 'checkpoint="Palembang"'


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

&lt;/div&gt;

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

&lt;p&gt;Thus my brief sharing for this tutorial. See you on another occasion.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Mhakimamransyah" rel="noopener noreferrer"&gt;https://github.com/Mhakimamransyah&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.linkedin.com/in/hakim-amr/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/hakim-amr/&lt;/a&gt;&lt;br&gt;
mailto: &lt;a href="mailto:m.hakim.amransyah.hakim@gmail.com"&gt;m.hakim.amransyah.hakim@gmail.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>microservices</category>
      <category>php</category>
      <category>rabbitmq</category>
      <category>messagebroke</category>
    </item>
  </channel>
</rss>
