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

می‌خواستم 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%B1-%D8%AA%D9%81%D8%A7%D9%88%D8%AA-%D9%87%D8%A7-ior92wjkaeua

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

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

اعتبار تصویر از ChatGPT
اعتبار تصویر از ChatGPT


خب تا اونجا اومدم که نم نم داشت اپکلیشنم بزرگ تر میشد. برنامه ای قصد داشتم بنویسم یه سیستم Task management بود. اولین قسمت چی بود؟ اینکه یه کاربر داشته باشیم. روال ثبت نام. روالی که همه مون بار ها و بار ها انجامش دادیم و میدونیم چطور کار میکنه ولی من کرمم گرفته بود (و البته کمالگرایی) که حتما ثبت نامم خاص باشه با همه مراحل از جمله verification. ولی حالا جلو تر بهش می‌رسیم.

یاد گرفتم چطور باید http request ها رو هندل کنم. یکم که جلوتر رفتم با یه کتابخونه به اسم mux آشنا شدم. میخواستم یه API داشته باشم که کاربر بتونه مثلا به register/ یه درخواست بزنه و ایمیل و پسورد بده و بهش بگیم که ثبت نام شدی. پس نیاز به routing داشتم، این همون چیزی بود که mux در اختیارم میذاشت. میتونستم handler ها رو به route ها مختلف assign کنم.

فعلا کاربر های ثبت نامی رو توی یه متغیر نگه میداشتم. خبری از دیتابیس نبود. نمیتونستم تو این مرحله برم سراغش؟ معلومه که میتونستم ولی نمیخواستم! میخواستم اول کار کردن با دیتا تایپ ها رو بهتر یاد بگیرم. قبلا توی JS میشد یه Array ساخت و کاربر هم میشد یه Object ولی اینجا داستان متفاوت بود و باید دیتا تایپ های جدید یاد می‌گرفتم. مثلا توی Go چیزی که ما توی JS میگفتیم بهش Array رو بهش میگه Slice و چیزی که تو JS می‌گفتیم Object بهش میگه Map. البته اینم در نظر بگیریم که رفتارشون قطعا تفاوت هایی داشت ولی من برای سادگی بیشتر اینجا بهشون اینطوری اشاره کردم.

خلاصه کنم، یه CRUD خیلی ساده روی همون Slice ها درست کردم. بهم کمک کرد درک بهتری از اون دیتا تایپ ها پیدا کنم. بعد از اون وقت این بود که این دیتا ها ذخیره بشن. اینجا میتونستم مستقیم برم سراغ یه دیتابیس مثل postgresql ولی گفتم باز هم ساده ترش میکنم. همه دیتا ها رو توی یه فایل db ذخیره کردم و به کمک sqlite بهش query زدم. ستاپ کردن اولیه sqlite هم جالب بود.

یاد گرفتن همین query ها خیلی لذت بخش بود. البته ناگفته نماند که فعلا هم دیتایی که باهاش سر و کار داشتم ساده بود، هم عملیات های روشون . فعلا خبری از join زدن و... نبود. بهرحال اینکار انجام شد.

روال ثبت نام و login رو پیاده کردم. بعد یه سری route جدید ساختم که بدون وجود توکن توی header از request کار نکنه. یه چیزی شبیه Guard داشتن. برای همین مسائلی مثل hash کردن پسورد و JWT هم لازم بود که هندل کنم و یاد بگیرم. البته قبلا (شاید ۴-۵ سال پیش) با Node همه اینکار ها رو انجام داده بودم و میدونستم که روال کار چجوریه. یا تو اپ های فرانت میدونستم که JWT چیه و اصولا چطور کار میکنه. فقط اینبار قرار بود با Go پیاده سازیش کنم.

برسیم به اون ماجرای فعال کردن یا Verify کردن کاربر ها. یه کد verification که تو زمان ثبت نام ساخته می‌شد و تاریخ مصرف داشت مثلا ۵ دقیقه یا ۱۰ دقیقه، برای تولید اون عدد رندوم از ChatGPT خواستم یه تابع برام بنویسه برای اینکار. بعد گفتم همونو برام توضیح بده خط به خط 😂 چون یه کارای عجیبی داشت می‌کرد که برام جالب بود. بعد از گذشتن تاریخ مصرف اون کد هم (حالتی که مثلا کاربر میخواد کد برای resend بشه) باید کد جدید براش ایجاد می‌شد. که در نهایت کاربر رو فعال می‌کرد و ادامه ماجرا...

در نهایت روال ثبت نام، فعالسازی کاربر، قابلیت ارسال مجدد کد فعالسازی و Login و میشه گفت کل Authentication کاربر انجام شده بود. عالی نبود ولی کافی بود.

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

بعد نوبت رسید به تعریف کردن مدل ها و انتقال دیتا ها از اون فایل ساده به یه دیتابیس بهتر (بهتر که میگم نه از نظر پرفورمنس یا ... ها) مثل همون PostgreSQL. پس شروع کردم ستاپ کردن سیستم و کد ها که بتونم دیتا ها رو ببرم اونور. برای اینکار صرفا باید Driver دیتا هام رو از SQLite تغییر می‌دادم به Postgres. البته روش query زدن ها هم یه مقداری تغییر کرد در حد سینتکس ولی کلیت همون بود.

یه نکته ای که اینجا بود این بود که هر زمان اپلیکیشن تغییر می‌کرد یا میخواستم نتیجه کد ها رو ببینم باید یه بار دیگه build می‌کردم، یعنی با هر تغییر فایل باید یه بار سرور رو میاوردم پایین و دوباره میاوردم بالا.

قبلا توی Node یه چیزی بود به اسم Nodemon که ازش برای همین کار استفاده می‌شد. اینجا هم یه سرچی زدم دیدم که بله، اینجا هم یه چیزی داریم که دقیقا همون کارو میکنه به اسم Air. پس اونم ستاپ کردم که سرعت dev بره بالاتر و کارم راحت تر بشه.

خلاصه دیتا ها رفت روی postgres و حالا وقتش بود که یکم model های جدید تر به سیستم معرفی بشه. اومدم یه تحلیل ریز از چیزی که میخوام بهش برسم انجام دادم:

  • کاربر باید عضو می‌شد که بتونه تسک بسازه.
  • کاربر میتونست چندین تسک مختلف داشته باشه.
  • هر تسک حاوی یه عنوان و توضیحات بود و وضعیت بود. (بعدا چیزای دیگه هم بهش اضافه کردم که میرسم بهش)

پس تا اینجای کار من دو تا model داشتم. اومدم و اونا رو تعریف کردم. نم نم داشت ارتباط بین این مدل ها شکل می‌گرفت. معنی Relational Database ها بیشتر داشت مهم می‌شد. پس باید مفاهیمی مثل کلید خارجی یا Foreign Key و Association و ... رو یاد می‌گرفتم. اما همچنان اصرار داشتم که به صورت Raw بیام و query هام رو بنویسم. چون میدونستم که اگه بخوام مستقیم برم سراغ ORM ها شاید عمق درکم از مسائل مربوط به دیتابیس کم بشه.

گرچه واقعا کسی که یه Backend Developer حتی جونیور هم باشه الان خیلی بهتر از من میتونه query خام بنویسه. یاد گرفتن query ها و مفاهیم خیلی مهم مثل Query optimization یا Indexing یا Sharding و Relication و ... و کلیییی چیز دیگه هست که من هنوز کار باهاشون رو تجربه نکردم. (ولی همین که میدونم این موارد وجود دارن بهم حس اعتماد به نفس میده که بعدا میتونم برسم سراغشون.

به مرور که داشتم API ها رو کامل ‌می‌کردم ایده این اومد به سرم که چرا هر تسک نتونه یه attachment داشته باشه؟ یه فایلی بشه بهش وصل کرد. اینجا یه راه ساده این بود که بیام مدل Task رو ویرایش کنم که فایل هم داشته باشه ولی بازم تجربه بهم میگفت که این روش خیلی reusable نیست. چون که اگه چند وقت بعد بخوام یه مدل دیگه به سیستم اضافه کنم که اونم بخواد یه سری فایل بهش اضافه بشه چی؟ اگه بعدا کاربر بخواد عکس پروفایل بذاره چی؟

پس فایل ها رو به شکل یه مدل مستقل ایجاد کردم که اسمش شد File که فارغ از موجودیت های مختلف برای خودشون ماهیت مستقل داشته باشن و بشه اونا رو به هر چیزی Assign کرد. مثلا کاربر به عنوان تصویر پروفایلش یه فایل عکس رو آپلود کنه و تسک هم بتونه یک یا چند تا فایل یا attachment داشته باشه و کلا هر چیز دیگه ای.

پس مدل File هم اینطوری متولد شد. حالا اون روابط داشت جالب تر می‌شد. هر کاربر میتونست یه تصویر پروفایل داشته باشه که درواقع یک مدل از File بود. هر تسک میتونست چندین attachment داشته باشه که باز هم یک مدل از File بود. دوباره Relational DB.

برای آپلود فایل ها یه API جدید ایجاد کردم ولی پیش خودم گفتم، باحال نمیشه اگه همه فایل ها (خود فایل) رو روی S3 ذخیره کنی و فقط URL اش رو توی دیتابیس خودمون؟ چه فرصتی از این بهتر که با AWS هم یکم کار کنم و یاد بگیرمش! مخصوصا حالا که use case های خوبی هم تو ذهنم که میخوام بهشون برسم.

پس وارد کهکشان AWS شدم. خب S3 یکی از ساده ترین سرویساش هست ولی برای اینکه وصلش کنم به اپلیکیشن واقعا دهنم سرویس شد. خب من که بکند دولوپر نیستم 😂 تجربه اولم هم بود. ولی آخر سر یه API داشتم که میشد از طریق upload/ صداش زد و فایل رو براش فرستاد و اون در جواب یه URL برمی‌گردوند که در واقع مسیر فیزیکی فایل روی S3 بود. حالا این URL (در واقع همون فایل) میتونست به هر جایی که ما میخوایم بچسبه! ایول!🤩 البته اینجا مدل فایل هم یکم تغییرات کرد مثل mime type و description و ...

خب یکم داشت برام عادی می‌شد کد زدن GO. انگار داشتم هی کارای تکراری انجام میدادم:

  1. به ریکوئست گوش کن و بفرستش دست handler خودش
  2. چک کن کاربر لاگین باشه و توکن اش درست باشه
  3. دیتا رو ذخیره کن و جواب بده به کاربر

تقریبا خیلی کد ها داشت توی اپلیکیشن تکراری می‌شد. ولی وجود داشتن یه سرویس third-party مثل همون S3 یه جذابیت تازه بهش بخشید. قبلا ها توی JS برای هندل کردن اینکارا از Promise ها استفاده می‌کردیم. میخواستیم اپلیکیشن به قول معروف پرمورمنس خوبی داشته باشه. گفتم بیام و همین سرویس رو توی GO ببینم چطور باید انجام بدم. یعنی به جای اینکه تابع رو اونجایی که باید با AWS ارتباط می‌گرفت به صورت Sync هندل کنم، بیام و Async هندلش کنم.

اینجا بود که با Goroutine و channel آشنا شدم. رفتم توی دلش. به نظرم واقعا برای کسی که از دنیای JS وارد میشد، فهم این مفاهیم توی Go خیلی ساده تر بود. لااقل من خیلی راحت فهمیدم ولی تو یوتوب که سرچ می‌کردم انگار از اون موضوعات خیلی سختی هست که همه باهاش درگیرن.

ناگفته نماند که از اونجا که مفهوم کد Sync و Async رو باهاش آشنا بودم از قبل، هم به فهم بهترش کمکم کرد هم میدونستم که edge case های خیلی خاصی داره و ممکنه race condition به وجود بیاد یا مثلا راجع به تفاوت نحوه اجراش توی Go نسبت به JS خیلی مطالعه کردم. نمیگم الان کامل و ۱۰۰٪ میفهممش ولی اندازه ای که لازمه برای کسی که ۲ ماهه وارد زبون جدید شده به نظرم کافیه و خوبه. البته چیزایی مثل Wait group ها هم هستن که به صورت تئوری باهاش آشنا شدم ولی هنوز جایی استفاده نکرده بودم تا اینجای کارم. (البته کاربردی هم برام نداشتن و منم اصراری نداشتم حتما توی کدم اونا رو بچپونم😁)

از این که بگذریم حالا یه اپلیکیشن داشتم که کاربر میتونست ثبت نام کنه و بعد از verify و login یه توکن بگیره و شروع کنه تسک جدید ساختن با عنوان و توضیحات و وضعیت (به این می‌رسم)، بعد یه فایل آپلود کنه و اون فایل رو به تسک Assign کنه یا در زمان ساخت تسک اصلا آدرس فایل هم بهش بده.

تا اینجا تسک ها هر کدوم همونطور که گفتم یه وضعیت داشتن. به صورت پیشفرض سه مورد براشون در نظر گرفته بودم که اینا بودن: "done" و "in progress" و "todo". این سه تا رو به صورت ثابت توی کد تعریف کرده بودم.


یه چیزی شده بود برای خودش تا اینجا. ولی آیا همین بود؟ نه! خیلی قرار بود موضوع پیچیده تر بشه. نم نم فهمیدم که برای اینکه ساختار دیتابیس رو بخوام تغییر بدم (مثلا یه فیلد جدید اضافه کنم به همون task یا مدل های جدید تر) باید اینکار رو به شکل امنی انجام داد. یعنی آشنایی با مفهوم Migration ها. قبلا خیلی دیده بودم و شنیده بودم. از دوستای بکند کارم یا توی کد ها خونده بودم. ولی خودم دست نزده بودم بهش. اینجا ولی وقتش بود (تقریبا)

پس اومدم و Migration ها رو به سیستم اضافه کردم. حالا میتونستم هر زمان که ساختار جدول هام تو دیتابیس عوض می‌شد اونا رو دنبال کنم. ورژن بزنم و کنترل کنم. البته هنوز خیلی جاهاش برای من گنگ هست ولی شروع خوبیه.




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

بعد تر میگم که چطور کنجکاوی من رو برد به سمت مسائل پیچیده تری از DevOps و داکر و مایکروسرویس ها. با سرویس های دیگه از AWS مثل IAM و EC2 و SES آشنا شدم.

هنوز کلی راه برای رفتن داشتم.


قسمت سوم رو اینجا می‌تونید بخونید:

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%B3-%D9%85%D8%B3%DB%8C%D8%B1-%D9%87%D8%A7%DB%8C-%D8%AC%D8%AF%DB%8C%D8%AF-u5iodboridsg


goبکندبرنامه نویسیاعتماد نفسبرنامه نویس
۱۳
۲
حسان امینی لو
حسان امینی لو
برنامه نویس از جلو
شاید از این پست‌ها خوشتان بیاید