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

می‌خواستم 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%B2-%D8%B2%DB%8C%D8%A8%D8%A7%DB%8C%DB%8C-%D9%87%D8%A7-chzqckhu4zlv

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


عکس از ChatGPT
عکس از ChatGPT


اول برنامه ریزی کردم. یه اپلیکیشن Task management رو گذاشتم به عنوان الگو برای خودم. البته به شکل خیلی ساده تر برای خودم تحلیلش کردم. برای همین Trello رو انتخاب کردم. دلیل این انتخاب هم این بود که Trello خیلی ساده تر بود نسبت به Jira و البته منم نمیخواستم یه اپلیکیشن تو اون ابعاد بسازم.

توی Trello کاربر ها میتونن:

  • یک یا چند پروژه جدید ایجاد کنن.
  • چند کاربر میتونن عضوی از یک پروژه یا صاحب چند پروژه باشن.
  • بعد توی اون پروژه بیان ستون های مختلف (Board) ایجاد کنن - مثلا حالت Kanban که ستون های Todo و In progress و done - داشته باشه.
  • میتونن این Board ها رو حرکت بدن و ترتیب شون رو عوض کنن.
  • میتونن توی هر Board یه تسک بسازن.
  • تسک ها رو میتونن بین این ستون ها جابجا کنن و حتی ترتیب شون رو عوض کنن.
  • تسک ها رو میتونن به یه نفر دیگه که عضو اون پروژه هست Assign کنن.

موارد دیگه مثل History داشتن هر تسک، Description های پیچیده تر مثل اضافه کردن Checklist به اون تسک و نشون دادن Progress هم بودن ولی گفتم برای شروع اسکلت اصلی Trello همیناست.

بعضی قابلیت ها رو هم که از قبل داشتم مثل همون فایل ها، ولی با این تحلیل من که البته خیلی ساده هست

نیاز بود دو تا Model دیگه به پروژه اضافه بشه:

  1. مدل Project که در واقع حکم جایی که توش ستون ها قراره بیاد رو داره
  2. مدل Board که هر پروژه میتونه چندتا (مثلا ماکزیمم ۱۰ تا) ازش داشته باشه + ترتیب داشتن اش.

از طرفی نیاز بود که بعضی Model ها هم تغییر کنن:

  1. مدل User حالا میتونست عضوی از یک یا چند پروژه باشه و در عین حال صاحب یک یا چند پروژه باشه.
  2. مدل User حالا میتونست به یک Task به عنوان Assignee اضافه بشه.
  3. مدل Task حالا باید زیر مجموعه ای از Board می‌شد (برخلاف قبل که بی صاحاب بود😁)
  4. مدل Task باید دارای ترتیب (Order) و Assignee می‌شد.

با این تفاسیر شروع کردم به ایجاد کردن مدل های جدید و ایجاد روابط شون توی دیتابیس. نم نم هندل کردن این روابط کار سختی داشت می‌شد. دیگه احساس کردم وقتشه که برم سراغ یه ORM که انتخابم شد Gorm که کار کردن باهاش تجربه خوبی بود.

یه موردی رو دوست دارم بهش اشاره کنم، اونم اینه که توی Go شما وقتی مثلا یه Type جدید ایجاد می‌کنی و میای المان های اون تایپ رو تعریف می‌کنی، خیلی شبیه TypeScript هست. مثلا این شخص اسم داره از جنس رشته.
ولی
علاوه بر تایپ اون پراپرتی ها یه flag یا descriptor هم میتونن هر کدوم داشته باشن. یعنی واقعا کارایی که تو این flag ها میشه کرد باور نکردنی هست. شما از validation بگیر تا تعریف مدل های gorm تا حتی اینکه توی json خروجی به چه اسمی میخوای این برگرده رو میتونی تو اینجا تعریف کنی. در عین سادگی بسیار کارآمد و خفن و خوبه.

توی این مدل جدید نیاز داشتم به خیلی API هایی که در واقع بیشتر کار PATCH رو انجام میدادن. اینجا بازم اونجایی بود که تجربه فرانتی خیلی به دردم خورد. چون میدونستم که کاربر ها اگه بخوان قابلیت اینو داشته باشن که مثلا فلان تسک رو از یه ستون بکشن یه ستون دیگه باید یه API باشه که این edit رو هندل کنه. پس روی API های مربوط به Task و Board و User این موارد رو لحاظ کردم. کاری که باید انجام می‌شد ساده بود.

  • تسک فلان رفت به ستون فلان با این عدد به عنوان ترتیب.
  • ستون فلان ترتیبش عوض شد به فلان عدد.
  • فلان یوزر assign شد به فلان تسک.
  • فلان فایل assign شد به فلان تسک.

همه اینا در واقع ریکوئست های PATCH بودن. بعضی هاشون payload داشتن توی body و بعضیا فقط یه param توی url شون و بعضیاشون هر دو.

در ضمن اینم بگم که کلا اپلیکیشن فقط یه سری API بود یعنی هیچ کد فرانتی براش زده نشده بود. ارتباط من هم باهاش فقط از طریق Postman بود.
همین کار باعث شد چقد با Postman هم بیشتر آشنا بشم. مثلا یه سری script نوشتم که بعد از login بیاد و توکن رو تو یه متغیر ذخیره کنه تا هربار مجبور نباشم کپی پیست کنم یا env های مختلف تعریف کردم (که البته چیز جدیدی نبود) یا response ها رو همه رو تو حالت های مختلف ذخیره میکردم توی اون collection ها.
حتی با AI اومدم یه سری script دیگه هم برای Test Automation روشون ایجاد کردم که مجبور نباشم هر دفعه بعد از هر تغییر خودم تست کنم. البته به تست های خود Go هم میرسیم که اونم یه چیز خیلی خفنیه اما من ایده ام این بود که ما که تا اینجا اومدیم، اینم بریم سمتش ببینیم چی به چیه.
خیلی رک بگم. چون فرانت کار میکنم خودم، خیلی دنبال این بودم که یه چیزی درست کنم که اگه خواستم اینو بدم یه نفر دیگه فرانتشو بزنه راحت باشه. چون میدونستم دغدغه های فرانتی ها رو.

به اینجا که رسیدم دیدم ای وای من...

دو تا اتفاق بد داره میوفته:

  1. همه ID هایی که داشتم توی دیتابیس، دقیقا همونایی بود که client داشت صدا میزد. یعنی اگه کاربر شماره id اش عدد 1 بود - دقیقا همینو تو همه API ها صدا می‌زدیم. این برای همه مدل های دیگه هم صادق بود. این چیزی نبود که من تو اپلیکیشن های واقعی دیده باشم.
  2. کد ها و توابع handler داشتن نم نم بزرگ می‌شدن. خوندنشون واقعا داشت سخت می‌شد.

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

طبق تحقیق خودم می‌خواستم بیام کلا یه چیزی مثل NanoID رو کلا جایگزین اعداد بکنم توی دیتابیس ام. یعنی کلا primary key ها و foreign key ها همه شون بشن NanoID ولی رفتم توی ستاپ کردنش دیدم اووووووووو این که خیییلی دردسر داره. باید یه hook ایجاد می‌کردم توی DB که مثلا وقتی فلان دیتا اضافه شد بیا یه NanoID بهش Assign کن. تایپ Primary Key رو عوض کن به VARCHAR و ... یه جای این موضوع بو های بدی میداد 😁 چرا باید یه تغییر انقدر پیچیده باشه؟

بعد از صحبت با همکارا و دوستان بکند کاری که دارم، فهمیدم میتونم به ازای هر عدد مثلا 1 یا 80 یا هر عدد دیگه ای، اون رو به کمک یه salt و یه کلید به یه رشته encrypt شده تبدیل کنم که قابل بازیابی باشه مثلا عدد 1 تبدیل بشه به uj7Bh34 و عدد 80 هم تبدیل بشه به iah73v6 و اگه همین رشته ها رو دوباره بخوام Decrypt کنم بتونم راحت برگردونمشون به 1 یا 80.

کلاینت که میخواد به من درخواست بده این رشته ها رو بفرسته و من به کمک اون کلید بفهمم که این منظورش چه عددی هست توی دیتابیس. از طرفی من هم قبل اینکه هر گونه id بخوام به سمت client برای هر کدوم از entity ها بفرستم این encryption رو انجام بدم.

پس با این روش دیگه خبری از کار کردن با hook های سمت دیتابیس نبود، دیگه قرار نبود تایپ primary key عوض بشه، قرار هم نبود فیلد جدیدی به هر مدل اضافه بشه. چه خوب! 🤩

خب مشکل حل شد!؟

نه!!!😳

مشکل درواقع حل شده بود ولی من ساده دیده بودمش یا بهتره بگم سطحی دیده بودمش. این موضوع اعداد و رشته ها حل شد ولی مساله دیگه ای که حالا وجود داشت این بود که اگه یه عدد مثل 1 همیشه برای همه entity ها از جمله Project, Board, Task و ... دقیقا تولید یک رشته می‌کرد! بنابراین مشکل حل نشده بود فقط شکلش عوض شده بود.

یکم سرچ کردم و فهمیدم میتونم این موضوع رو - تولید عدد encrypt شده و بعدا decrypt کردنش - برای هر کدوم از entity ها با یه مقدار از پیش تعیین شده شکلش رو عوض کنم. حالا هر id برای هر کدوم از entity ها با وجودی که توی دیتابیس به یک عدد (مثل همون ۱) اشاره می‌کردن ولی رشته های مختلفی ازشون درست شده بود که البته خیلی ساده هم بعد از request برمی‌گشتن به عدد اصلی شون توی دیتابیس.

این اعداد ثابت هم میتونستن هر چیزی باشن (مثلا برای مدل User من عدد 11 رو گذاشتم، برای Board عدد 33 و همینطوری الی آخر). صرفا در زمان encrypt و decrypt شدن مدل ها باید اینم لحاظ می‌کردم.

پس اینم آشنا شدن بیشتر من با مفاهیم encrypt و decrypt و NanoID و حتی هوک های دیتابیس.

از NanoID استفاده کردم چون UUID خیلی طولانی بود و نمیخواستم اونقدر طولانی باشن (مثلا UUID طولش شاید بشه ۲۵ کاراکتر ولی NanoID طولش میشه 6 تا 8 کاراکتر، دست خودمونه) - از طرفی تا جایی که من سواد و تجربه داشتم میدونستم اگه بخوام UUID نگه دارم باید یه فیلد جدید توی دیتابیسم درست کنم که کمکی به Business Logic نمی‌کرد و خبری هم از decrypt شدن نبود.


مشکل دوم ریفکتور کردن پروژه بود. یکم حالا وقت داشتم یه دستی به کد بکشم. یکم سرچ کنم ببینم چه روش های بهتری هست و برم سمت اونا. مثلا قبلا اگه یه کاری رو توی دو خط کد داشتم انجام میدادم تو یه خط حالا انجامش دادم. از مسائل سینتکسی که بگذریم رفتم سراغ اون سیستم routing.

قبلا داشتم از Mux استفاده می‌کردم. برای خوندن بدنه request ها هم دستی داشتم انجامش میدادم و دستی هم داشتم response ها رو هندل می‌کردم. البته یه سری ‌‌helper برای نوشته بودم ولی بازم دستی بود. کلا هرچی نوشته بودم تا اون موقع در هدف کار کردن اپلیکیشن بود.

پرفورمنس برام مطرح نبود. فقط کار کنه کافیه.

خب من قبلا با چیزایی مثل Express توی Node آشنا بودم، Laravel هم دیده بودم در مورد Django هم شنیده بودم ولی ندیدم تاحالا😁. میدونستم که خیلی کار ها رو میتونن ساده تر کنن. پس فکر کردم که وقتشه برم سراغ یکی از این فریم ورک ها ولی توی GO. یه سرچی زدم دیدم آپشن زیاده. برام مهم نبود پرفورمنس کدومشون بهتره یا سریع تر هستن یا ... برام کامیونیتی مهم بود و سادگی شون. خداروشکر تو همه شون هم که میری نوشته سریع ترین و فلان ترین و ...😒 در نهایت انتخابم شد Fiber.

خب یه مزیتی که Fiber داشت این بود که شباهت بی حد و اندازه ای به Express داشت که قبلا من کار کرده بودم، یعنی خیلی این موضوع Middleware ها توش مطرح بود. اومدم و اپلیکیشن رو خلاصه ریفکتور کردم و بردمش روی Fiber. این وسط کلی چیز حذف شد. کلی لاجیک دیگه رفته بود توی Middleware ها به جای اینکه توی هر handler تکرار بشه مثلا اون چک کردن auth. خلاصه اپلیکیشن یه مقدار پوست انداخت.

تو مرحله بعد فکر کردم که چرا اون قسمت verify شدن کاربر ها رو به کمک Email هندل نکنم؟ یعنی واقعی به کاربری که ثبت نام میکنه ایمیل بزنم. برای اینکار دوباره رفتم سراغ AWS و این دفه با سرویس SES آشنا شدم. کانفیگ کردم و کد نوشتم و خلاصه اولین ایمیل فرستاده شد. البته فقط برای ایمیل های Verify شده که از قبل خودم تعریف کرده بودم نه به صورت public چون اون داستانای خودشو داشت که حالا واردش نمیشم. این وسط البته من فقط گفتم کانفیگ کردم و تمام و شما تو یه جمله میخونی و رد میشی ولی واقعا کانفیگش کلی زمان و انرژی گرفت 😅

بعد گفتم چرا این ایمیل ها بخوان زشت باشن، چون تا اون موقع هر ایمیل فقط توش یه متن بود با یه کد ۶ رقمی فعال سازی. میدونستم که برای اینکه ایمیل ها خوشگل بشن باید یه کد HTML بفرستم به جای این متن. یعنی البته اون html رو به شکل یه string بدم بهش.

پس رفتم سراغ Template Engine ها توی Go و یه Template ایمیل دانلود کردم به صورت HTML و از اون به عنوان قالب ایمیل ها استفاده کردم. اینجا بود که یاد گرفتم بعدا (در آینده) اگه بخوام مثلا صفحات static یا فرانتی هم parse کنم چجوری اینکار انجام میشه.

بعد گفتم اصلا حالا تا اینجا که اومدیم، چرا اینو deploy نکنم؟


قسمت بعد میرم سراغ ماجرای دیپلوی کردن اپلیکیشنم روی AWS و دردسر هاش و چیزایی که بهم یاد داد مثل Docker و Docker compose و Nginx و ... چیزایی مثل caching هم دیدم که به کمک Redis هندل کردم و چالش های خودش.

بعد اینکه چطور شد که یه دستی به سر رو روی اپلیکیشن تو محیط Dev کشیدم و کارم رو با داکر ساده تر کردم، جلوتر هم دیگه پا رو فراتر گذاشتم و گفتم برم سمت Microservice ها. اونجا با کلی مفهوم دیگه آشنا شدم و معماری های مختلفی رو دیدم. این اپلیکیشن که با ایده کپی از Trello بوجود اومده بود داشت نم نم بزرگ تر می‌شد و به من کلی چیز یاد می‌داد.


قسمت چهارم:

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پروژهبکندbackendبرنامه نویسی
۱۴
۲
حسان امینی لو
حسان امینی لو
برنامه نویس از جلو
شاید از این پست‌ها خوشتان بیاید