ویرگول
ورودثبت نام
حسان امینی لو
حسان امینی لوبرنامه نویس از جلو
حسان امینی لو
حسان امینی لو
خواندن ۱۲ دقیقه·۷ ماه پیش

می‌خواستم GO یاد بگیرم، پس اینکارو کردم - قسمت آخر - کار ناتمام؟

اگه قسمت قبل رو نخوندید، اینجا پیداش می‌کنید:

https://virgool.io/@hesanam/%D9%85%DB%8C-%D8%AE%D9%88%D8%A7%D8%B3%D8%AA%D9%85-go-%DB%8C%D8%A7%D8%AF-%D8%A8%DA%AF%DB%8C%D8%B1%D9%85-%D9%BE%D8%B3-%D8%A7%DB%8C%D9%86%DA%A9%D8%A7%D8%B1%D9%88-%DA%A9%D8%B1%D8%AF%D9%85-%D9%82%D8%B3%D9%85%D8%AA-%DB%B4-%D8%A7%DB%8C%D9%86-%DA%86%D9%87-%D8%AC%D9%87%D9%86%D9%85%DB%8C%D9%87-n8hh1ojsbr1r

بذارید داستان رو اینطوری ادامه بدیم، من کجا ایستاده بودم الان؟ یه اپلیکیشن نوشته بودم به کمک Go و چندتا کتابخونه دیگه عملا یه کاری رو داشت انجام می‌داد. اگه یه نفر هم بود که سمت فرانت-اند پروژه رو انجام می‌داد، شاید میتونست که پروژه کامل بشه و حتی ازش استفاده هم کرد.

همه API ها توی همون حالت Monolith داشتن کار می‌کردن، سرویس ارسال ایمیل داشت به خوبی کار می‌کرد، پروژه روی داکر بود، با gitlab ci هم به خوبی داشت کار می‌کرد، روی AWS بالا بود. این وسط بار ها و بار ها هم من ساختار فایل ها و فولدر ها رو عوض کردم. کلی توابع رو خوانا تر کردم و idiomatic تر هم شده بود. یعنی چی؟

یه موضوعی که توی Go هست اینه که که مدلی داره برای خودش. منظورم چیه؟ در حین یادگیری، یه چیزی که بهش خیلی برمی‌خوردم این کلمه بود: Idiomatic Go یا Golang idioms. یعنی زبان کاملا دست شما رو باز گذاشته هر طوری که دوست دارید برنامه تون رو بنویسید ولی این وسط یه سری اصول یا قاعده وجود دارن که باعث میشه بگن این اپلیکیشن خوب نوشته شده و اگه رعایتشون نکنی دنیا تموم نشده ولی انگار مدل فکر کردنت به کد باید طور دیگری می‌بود. باید به سبک Go فکر کنی. کدت باید گوئی تر (Go-ish) باشه. 😅

اما برای من که از دنیای فرانت وارد داستان شدم این موضوع خیلی قابل پذیرش بود. کلا JS هم خیلی این فرم رو داره. البته به اندازه Go جدی نیست ولی اونم تقریبا چنین چیزی رو داره. بذارید با مثال بگم حرفم رو

خیلی فریم-ورک ها شما رو مجبور میکنن که یه مدل مشخص کدتون رو بنویسید. یا فولدر ها و فایل ها رو یه طور خاصی دسته بندی کنید. مثلا توی NextJS شما مفاهیمی مثل App Router دارید. این یعنی یه مقدار Opinionated هست. انگار توی بعضی فریم-ورک ها، یک نظری یا شیوه فکر کردنی به شما تحمیل شده ولی خود زبان دست شما رو باز گذاشته که هر طور دوست دارید بنویسید. نمونه مقابلش هم هست که مثلا توی Java شما باید حتما OOP بلد باشید. ولی توی Go اصلا OOP شکل دیگری پیاده سازی میشه که متفاوته. همین موضوع توی JS هم صادقه. ایده رو امیدوارم گرفته باشین.

توی Go هم تقریبا چنین داستانی وجود داره. زبان کامل دست شما رو باز میذاره هر کاری دوست دارید انجام بدید. ولی یه سری استاندارد هست که بهتره رعایت کنید. کلی اصول و قاعده وجود داره که از نام گذاری فایل ها و فولدر بندی بگیر تا شکل نوشتن شرط ها... که بهتره رعایت بشن. انگار سیم پیچی مغزم داشت عوض می‌شد. خورد خورد سعی کردم نکاتش رو یاد بگیرم و توی کدم پیاده کنم و اگه لازم بود تغییر بدم.

این موضوع رو بهش خواستم فقط اشاره کوچیکی بکنم، که بگم کارم با Go تموم نشده بود و همزمان با درگیری های دیگه که داشتم سعی می‌کردم مهارت هام توی خود زبون رو هم بهبود بدم و بیشتر یاد بگیرم.

کل این داستانی که براتون توی ۵ تا یادداشت خلاصه کردم، حاصل سر و کله زدن ۴-۵ ماه من بوده. پس اینو گوشه ذهن داشته باشید که همه این چیزا توی ۵ روز اتفاق نیوفتادن. پس من وقت زیادی داشتم که روی خیلی چیز ها کار کنم.

بریم بقیه ماجرا.


رسیدیم به نقطه ای که من تصمیم گرفتم اپلیکیشن رو با معماری Microservice یه طورایی بازنویسی کنم. چون دیگه این اسمش ریفکتور نبود. توی معماری Microservice هر سرویس باید در حالت ایده-آل دیتابیس خودش رو داشته باشه. چرا حالا میگم ایده‌-آل؟ چون شما میتونی مدلی پیاده سازی کنی که مثلا چند تا سرویس به یک دیتابیس متصل باشن ولی تو حالتی که بخواد best practice باشه باید اینا از هم جدا باشن و کاملا مستقل.

خب منم مشکلی با این موضوع نداشتم. پس شروع کردم سرویس ها رو جدا کردن. برای جدا کردن سرویس ها از هم دیگه یه شمای کلی از سیستمی که داشتم باید ترسیم می‌کردم. خب هر سرویس باید وظایف خودشو فقط انجام می‌داد. مثلا توی Auth فقط چک کردن و امور مربوط به JWT و Credentials بود ولی ایجاد کاربر جدید باید یه سرویس دیگه به اسم User درگیرش می‌شد.

۳ سال پیش یه کتابی به من معرفی شد با عنوان Domain Driven Design. اون زمان وقتی میخوندم کتاب رو خیلی برام گنگ و نامفهوم بود. کلا چون ذهنم مثل امروزم کار نمی‌کرد. خیلی سختم بود بفهمم، برای همینم اون موقع نصفه خوندم و ولش کردم و بعد ها متاسفانه کتاب رو گم کردم، ولی میتونید تو این مطلب با مفهوم کلیش بیشتر آشنا بشید.

بعد از بررسی های خودم و نظرات سازنده AI و کمک و درک نصف و نیمه ام از همون DDD باعث شد من تصمیم بگیرم سیستم رو اینطوری به سرویس های کوچک تر بشکنم:

  • سرویس Auth: وظیفه کنترل کردن Credentials به شکل کلی

  • سرویس User: وظیفه امور مربوط به یوزر (ساخت و ادیت هر عملیاتی رو مدل User)

  • سرویس Task: هر عملیاتی که روی یک تسک میتونست انجام بشه

  • سرویس Upload: هر عملیاتی مرطبت با فایل ها

  • سرویس Project: مدیریت و کنترل پروژه ها به همراه Board ها

  • سرویس Gateway: ارتباط و هماهنگی بین همه سرویس های دیگه

شاید این تقسیم بندی درست باشه، شایدم نباشه. فعلا که اینطوره! بر اساس Domain های مختلف سعی کردم این سرویس ها رو اینطور جدا کنم از همدیگه. هر کدوم از سرویس ها دیتابیس خودشونو داشتن. اینجا اگه مخاطب باهوشی که شما باشی، میگی پس بعضی چیزا مثل ارتباط بین همون auth و user چی؟ یا ارتباط بین task و user و ارتباط بین task و project و board. اگه اینا بخوان هر کدوم دیتابیس مستقل داشته باشن چی میشه؟

خب بذارید اینطور بگم. ارتباط بین این سرویس ها از روش های مختلف باید انجام بشه. یه دونه از سناریو های ساده رو بیاید با هم مرور کنیم: سناریو ثبت نام کاربر

  1. درخواست از سمت کاربر میاد به register/ که سرویس Gateway داره گوش میده

  2. سرویس Gateway درخواست رو با توجه به اینکه مربوط به Auth هست میده به اون

  3. سرویس Auth اطلاعات رو از body میخونه، اول از سرویس User می‌پرسه که این کاربر وجود داره یا نه؟

  4. اگه سرویس User ببینه وجود نداره، یوزر رو میسازه و میذاره توی دیتابیس خودش و به Auth خبر میده

  5. اگه سرویس User ببینه وجود داره از قبل، به Auth میگه قضیه کنکله چون این از قبل بوده

  6. در هر صورت سرویس Auth به Gateway میگه که این جواب منه (مثبت یا منفی)

  7. سرویس Gateway به سمت کاربر جواب رو میفرسته (مثبت یا منفی)

من خیلی سعی کردم سناریو رو کامل تشریح کنم ولی خیلی چیزا این وسط از قلم میوفته ولی از طرفی اشاره کردن بهشون مطلب رو طولانی میکنه. نحوه ارتباطات اشاره نشده، validation input ها اشاره نشده و کلی چیز دیگه که شما بر من ببخشید.

همینطور که می‌بینید، توی این مدل معماری کلی سرویس داریم که باید با همدیگه صحبت کنن. مثل کارمند های یه اداره، برای انجام یه کار اداری - مثل این میمونه که شما یه درخواست دارید، ولی کلی کارمند اون پشت باهم باید ارتباط بگیرن، تاییدیه بگیرن و در نهایت به شما جواب بدن - حالا توی بعضی اداره ها خیلی وقت ها این رفت و برگشت ها رو خودمون به عنوان کاربر انجام می‌دیم، ولی بعضی جاها هم اتفاقات پشت پرده اتفاق میوفته.

وجود یه سرویس به اسم Gateway هم به همین منظوره، اون وظیفه اش اینه که درخواست رو بگیره و بده دست سرویس مربوط به خودش و در نهایت هم جواب رو بگیره بده به شما. اون پشت شاید چندتا سرویس مجبور بشن به هم دیتا رد و بدل کنن. البته ضمنا اون قضیه encrypt و decrypt شدن هم میتونه اینجا اتفاق بیوفته.

اینجا اصلا یه سری use case جدید شکل گرفته بود، مثلا قبلا تو حالت monolith اصلا من API ای برای چک کردن اینکه کاربر وجود داره یا نه نداشتم! ولی اینجا لازم بود! یعنی یه API که عملا فقط کارش این بود که یه سری id بگیره و بگه اینا وجود دارن یا نه. یه internal API که فقط توسط سرویس های دیگه صدا زده بشه.

اصلا پس با این تفاسیر وجود اون Gateway ضروری هم بود چون که اون فقط API هایی رو از سرور در اختیار قرار می‌داد که خودمون بخوایم.

ولی اشاره کردم به صحبت کردن بین این سرویس ها، یه راه خیلی ساده که وجود داره اینه که سرویس ها مستقیم باهم صحبت کنن. مثلا در زمان همون ثبت نام، سرویس Auth از User به کمک یه Rest API ارتباط بگیره. در این حالت حالا میشه این ارتباط به شکل sync یا async هم باشه که این البته وابسته به سناریو و use case هست که می‌خوایم سریع به کاربر جواب بدیم یا می‌تونیم بعدا بهش جواب بدیم.

پس تو روش اول داریم از داخل سرور، از یه سرویس به یه سرویس دیگه ریکوئست می‌زنیم.

روش دیگه خیلی شبیه به همینه، با این تفاوت که این دفعه به جای Rest از gRPC استفاده کنیم. خب این روش دردسر های خودشو داره، مزایایی هم داره. مثلا سریع تره، ساختارمند تره و ... ولی من شخصا نرفتم سمتش. اما شیوه کارش رو فهمیدم.

یه روش دیگه اینه که اصلا بیایم یه واسطی رو این وسط قرار بدیم که این سرویس ها کلا از طریق اون باهم در ارتباط باشن. یعنی یه چیزی بین این چندتا سرویس قرار بگیره که هر سرویسی با جای دیگه کار داشت بره به این بگه و این به بقیه.

اینجا بود که با مفهوم Event Driven Design آشنا شدم! خیلی خلاصه یعنی یه سرویسی بلند داد میزنه میگه فلان اتفاق افتاد، بقیه سرویس ها که براشون اون اتفاق مهم باشه گوش میدن و یه کاری رو انجام میدن، برای بقیه اگه مهم نباشه خب کاری هم نمیکنن. این تعریف خیلی خیلی خیلی خیلی خلاصه از از معماری pub/sub هم هست. یه event رو publish می‌کنیم و توی یه سرویس دیگه به اون event خاص subscribe.

این روش به نظرم خیلی جذاب اومد! scale شدنش خیلی خیلی راحت تر می‌تونست انجام بشه، coupling بین سرویس های مختلف هم کمتر می‌شد. از طرفی از نگاه DevOps هم که بخوایم بهش نگاه کنیم، اینجا می‌تونستیم مثلا تعداد instance های یه سرویس پر کاربرد تر رو بیشتر کنیم یا تعداد اون واسط ها رو بیشتر کنیم یا هرکار دیگه ای.

این واسط که دارم راجع بهش صحبت می‌کنم میتونه چیزای مختلف باشه. شاید شاخص ترینش Kafka باشه. ولی من از Nats استفاده کردم. چون همینکارو دقیقا انجام می‌داد برای من ولی خیلی برام ساده تر بود پیاده سازیش. توی بیزنس های بزرگ (و البته واقعی) این کار رو میتونن روی سرویس های ابری هم هندل کنن که هم AWS دارتش، هم Google و هم Microsoft. من دقیقا نمیدونم چیه اسم این سرویس ها تو هر کدوم اینا ولی هست.

البته اینم بگم که من اینجا اشاره کردم که این واسط بین سرویس هاست ولی خیلی کارای بیشتری هم انجام میدن. تو اپلیکیشن های با scale های خیلی وحشتناک که میلیون ها درخواست در ثانیه رو هندل می‌کنن یه چیزی مثل Kafka دیگه فقط یه Message Broker نیست.

بخوام راجع بهش بنویسم نه اینجا جاش هست و نه شما حوصله اش رو دارید. اینو می‌سپرم به عهده خودتون.

در هر صورت، من تصمیم گرفتم این روش رو پیاده سازی کنم ولی دقیقا این همونجایی بود که زیرش زاییدم 😂 توی حالت استاندارد و معماری تر و تمیز Microservice خیلی چیز ها باید سر جای خودشون دقیق قرار بگیرن که بگیم ما معماری Microservice رو درست پیاده کردیم. این کمالگرایی (یا شاید ماجراجویی من) من رو به این نقطه رسوند که الان که دارم این مطلب رو می‌نویسم هنوز همه API ها مثل حالت Monolith دیگه کار نمیکنن. باید کلی راه دیگه برم تا دوباره برسم به اون نقطه.

ولی چه مسیری بوده تا الان، چه قدر چیز یاد گرفتم، چه قدر زبان مشترک پیدا کردم با دنیا های جدید و مختلفی که قبلا راجع بهشون چقدر کم میدونستم و الان هم کم میدونم ولی نقطه خوبی ایستادم و از خودم راضی هستم. البته بعد اینکه اپلیکیشن رو کامل کنم راضی تر هم میشم 😁


ایده هایی تو سرم هست برای ادامه پروژه:

  • دوست دارم یه مدل Log درست کنم برای تسک ها، یه چیزی شبیه history

  • سمت فرانت اپلیکیشن رو پیاده سازی کنم ولی نه با Next یا React بلکه با یه تکنولوژی جدید مثلا Vue و Nuxt. چرا؟ چون میخوام اونا رو هم یاد بگیرم و یه امتحانی کرده باشم 😅

  • سمت فرانت زمانی که بخوام پیاده سازی رو شروع کنم، اون موضوع drag and drop رو دوست دارم بهش توجه کنم

  • حتما موضوع هایی مثل Load Test و ... رو دوست دارم امتحان کنم. مثلا ببینم چقدر میتونم به کمک K8s یا Docker اپلیکیشن رو scale کنم.

  • امکان کار همزمان چند نفر روی یک Project به کمک Web socket - یه چیزی شبیه google docs که میتونی ببینی دقیقا اون یکی کاربر داره چیکار میکنه و کجاست

  • امکان اضافه کردن موارد جدید به هر تسک، مثلا همون Checklist یا تعیین Deadline براش و ...

اینم راجع به تست بگم که تو قسمت های قبل یادم رفته بود: توی Go به صورت built-in شما میتونین تست بنویسید و با یه دستور اجراشون کنید، یعنی هیچ کتابخونه و خرت و پرتی نمیخواد. اینم یکی دیگه از چیزایی بود که برای من خیلی جذاب بود.

در نهایت باید بگم تا اینجای این مسیر برای من بسیار پر چالش ولی لذت بخشی بود. خیلی وقت بود که از چیزی به این اندازه لذت نبرده بودم ولی این مسیر همچنان ادامه داره و هنوز کامل نشده. شاید یه روز که همه چیز انجام بشه لینک پروژه رو به همراه کد هاش روی گیت هابم بذارم که بتونید ببینیدش. تا اون موقع شاید خیلی زمان مونده باشه ولی یه روز تکمیل میشه.


جا داره از منابع (یا ابزار) که استفاده کردم تو این مسیر هم نام ببرم.

  • کانال یوتوب TechWorld with Nana به شکل قابل توجهی مخصوصا برای قسمت های DevOps (البته رو دور تند چون خیلی ویدئو باز نیستم😅)

  • مقاله های Medium

  • ابزار های AI مثل ChatGPT و Claude

  • سایت Codecrafters (البته منبع نمیشه بهش گفت، بیشتر تمرین بود)

  • خود سایت Go و البته این لینک

  • سایت Reddit و البته گوگل به شکل خیلی زیادی

اینم موزیکی بود که در حین کار خیلی گوش میدادم 😁 حتما پیشنهاد می‌کنم یه بار گوش بدید - لذت ببرید.

چندتایی کتاب هم بودن که عکسشونو این پایین میتونید ببینید.


یه توضیحی هم بدم راجع به روش استفاده ام از AI - بعدا راجع به این موضوع یه مطلب مفصل می‌نویسم و شیوه کار خودم و تجربه ام رو توضیح میدم. ولی به طور کل کاری که من کردم این بود که خیلی سعی می‌کردم سوالاتی که ازش می‌پرسم رو با prompt های خوب و هدف مند انجام بدم. یعنی چی؟ یعنی هر زمان که هر کاری میخواستم انجام بدم و مطمئن نبودم بهش، ازش میخواستم بهم چندتا منبع معرفی کنه. خیلی جاها سعی می‌کرد خودش کد رو برام بنویسه ولی این مدلی منو خیلی تنبل می‌کرد. ازش خواسته بودم که کد ننویسه برام - در عوض بهم مسیر بده و صرفا hint بده و بذاره خودم اینکارو بکنم. قطعا در آینده اگه بخوام به شکل حرفه ای تر Backend رو دنبال کنم و دستم خیلی راحت تر بشه با Go و فضاش خیلی بیشتر ازش برای نوشتن کد ها استفاده می‌کنم. ولی فعلا دوست دارم بذاره خودم فکر کنم و دستم راه بیوفته. مثل خاموش کردن autocomplete میمونه.


در آخر دمتون اگه مطلب رو خوندید، حتما خوشحال میشم نظرتون رو بدونم.


برنامه نویسیgoآموزش برنامه نویسیdevops
۱۱
۰
حسان امینی لو
حسان امینی لو
برنامه نویس از جلو
شاید از این پست‌ها خوشتان بیاید