مدیریت وابستگی ها در زبان Go با استفاده از ماژول ها

مدیریت وابستگی ها (dependency management) در نسخه های اولیه زبان Go با مشکلات زیادی همراه بود. ابزارهای متن باز مختلفی در این بازه تلاش کردند تا شکاف و ضعف موجود در زبان را مرتفع کنند. از این میان می توان به پروژه های dep و govendor اشاره کرد که تلاش های قابل قبولی برای مدیریت وابستگی ها در زبان داشتند اما مشکلاتی مانند تشخیص نسخه های وابسته را در خود داشته که قادر به حل آن نبودند. از نسخه 1.11 ماژول ها به زبان Go اضافه شدند که به صورت رسمی وظیفه مدیریت نسخه ها و وابستگی ها (ماژول های جانبی) را بر عهده گرفتند. هدف از ارائه این قابلیت، یکپارچه سازی تمام ابزارهای موجود در این زمینه برای ایجاد ساختار و شکل استاندارد در پروژه های زبان Go بود. Go Modules چیست؟بر اساس توضیحات ابزار:یک ماژول مجموعه ای از بسته های مرتبط به هم است که در قالب یک نسخه در یک واحد، جای گرفته اند. در بیشتر مواقع، یک مخزن کنترل کد (version control repository) دقیقا با یک ماژول مطابقت دارد اما در بعضی موارد یک مخزن می تواند شامل چند ماژول باشد.ماژول ها برای نسخه های خود، از سیستم نسخه دهی معنایی در قالب زیر استفاده می کنند:v(major).(minor).(patch)ماژول ها الزامات وابستگی را به صورت دقیق ثبت کرده و نسخه های قابل بازتولید ایجاد می کنند. هر ماژول در فایل go.mod در شاخه اصلی پروژه تعریف می شود. به عنوان نمونه فایل زیر:module go-rest-example go 1.14 require github.com/gin-gonic/gin v1.6.2با معرفی ماژول در نسخه 1.11 راه برای از رده خارج شدن GOPATH باز شد. بنابراین با استفاده از ماژول ها دیگر نیازی نیست که پروژه های خود را در پوشه src درون GOPATH ایجاد کنید.انتخاب نسخه حداقلیاین دیدگاه می خواهد که بهترین و قدیمی ترین نسخه ای از وابستگی که با پروژه فعلی سازگار است را انتخاب کند. بهترین نسخه، نسخه ای است که کاربر وارد می کند. اگر شما در فایل go.mod مشخص کنید که می خواهید از نسخه 1.4 استفاده کنید و نسخه 1.5 هم موجود باشد، Go از نسخه 1.4 استفاده خواهد کرد.اگر نسخه 1.4 توسط کاربر مشخص شود و یک وابستگی دیگر نیاز به نسخه 1.5 داشته باشد، Go از نسخه 1.5 استفاده خواهد کرد چرا که این نسخه با توجه به نیاز پروژه قدیمی ترین نسخه معتبر است.Semantic Import Versioning چیست؟این اصل که به اختصار SIV نامیده می شود مشخص می کند که نسخه های اصلی باید شماره نسخه خود را در مسیر ورود مشخص کنند. بنابراین github.com/example/test برای نسخه دوم به github.com/example/test/v2 و برای نسخه سوم به github.com/example/test/v3 تبدیل می شود. بر اساس قرداد نسخه های پیش از دوم نیازی به قرارگیری در مسیر ورود ندارند.کار با Go modulesپیش از شروع کار این نکته را در نظر داشته باشید که مسیر تست باید خارج از GOPATH باشد چرا که پشتیبانی از ماژول ها در این مسیر به صورت پیش فرض غیرفعال است. برای استفاده از این قابلیت باید آن را فعال کنید.$ export GO111MODULE=on;برای ادامه مسیر فرض می کنیم در مسیری خارج از GOPATH قرار گرفته اید. برای شروع کار یک پوشه خالی بسازید و ساخت ماژول را آغاز کنید.$ mkdir tgomod $ go mod init github.com/mehrdadep/tgomod $ vim mod-test.goنسخه اول ماژول شامل function ساده ای برای چاپ ورود ما به دنیای ماژول هاست.package tgomod import &quotfmt&quot // Print prints String &quotHello Modules!&quot func Print() { fmt.Println(&quotHello Modules!&quot) }این کد در شاخه اصلی پروژه و فایل main.go قرار می گیرد. اکنون فایل go.mod به شکل زیر است:module github.com/mehrdadep/tgomod go 1.14برای دریافت ماژول ها از دستور زیر استفاده می شود. برای اینکه ماژول ما برای کاربران دیگر قابل دسترس باشد باید آن را روی github قرار دهیم.$ go get path/to/module اولین commit پروژه را انجام داده و آن را روی github قرار می دهیم.$ git init $ git add . $ git commit -m &quot:tada: Initial commit&quot $ git remote add origin https://github.com/mehrdadep/tgomod.git $ git push -u origin masterانتشار اولین نسخهحالا که بسته ما آماده انتشار است می توانیم آن را با دنیا به اشتراک بگذاریم. برای این کار باید از برچسب های نسخه استفاده کنیم. نسخه اول ما 1.0.0 خواهد بود.$ git tag v1.0.0 $ git push --tagsاستفاده از ماژولبرای استفاده از ماژول باید یک برنامه کوچک دیگر ایجاد کنیم و ماژول خود را import کنیم.package main import ( &quotgithub.com/mehrdadep/tgomod&quot ) func main() { tgomod.Print() }برای دریافت ماژول و اجرای پروژه از دستورات زیر استفاده کنید.$ go mod init mod $ go buildبا اجرای دستور دوم مشاهده می کنیم که Go به صورت خودکار ماژول تست ما را دریافت کرده و در پروژه استفاده می کند. اگر نگاهی به فایل go.mod پروژه تست بیندازیم مشاهده خواهیم کرد که ماژول ما به آن اضافه شده است:module mod go 1.14 require github.com/mehrdadep/tgomod v1.0.0پس از اجرای این دستورات فایل دیگری در کنار go.mod ایجاد شده است. فایل go.sum شامل checksum بسته های دریافت شده برای اطمینان از صحت نسخه دریافت شده است.github.com/mehrdadep/tgomod v1.0.0 h1:L9YscrHW8SlOtWEv849UgCcMrmpU0E9AOAaPmfu7GxU= github.com/mehrdadep/tgomod v1.0.0/go.mod h1:YIkzdF7Sf9nd+eC0ySxL+gGbsew7LvUh9vP3p7yzTi4=انتشار نسخه رفع باگبرای انتشار نسخه رفع باگ ابتدا کد موجود در ماژول خود را بروزرسانی می کنیم و سپس نسخه 1.0.1 را انتشار می دهیم.package tgomod import &quotfmt&quot // Print prints String &quotHello Modules!&quot func Print() { fmt.Println(&quotHello Modules (patched)!&quot) }اکنون نسخه جدید را با دستورات زیر منتشر می کنیم.$ git add . $ git commit -m &quot:bug: Minor bug fix on Print&quot $ git push $ git tag v1.0.1 $ git push --tagsبه دلیل اینکه این نسخه، نسخه اصلی نیست نیازی به تغییر مسیر ورود ماژول در برنامه تست نیست و تنها کافی است ماژول ها را بروزرسانی کنیم. با هر یک از دستورات زیر امکان بروزرسانی برنامه تست وجود دارد.$ go get -u $ go get -u=patch $ go get github.com/mehrdadep/tgomod@v1.0.1انتشار نسخه اصلی برای انتشار نسخه اصلی کد ماژول را به شکل زیر تغییر دهید. این نسخه را v2 نامگذاری می کنیم.package tgomod import &quotfmt&quot // Print prints String &quotHello Modules!&quot func Print(s string) { fmt.Printf(&quotHello Modules %s!&quot, s) }از آنجا که در حال انتشار یک نسخه جدید اصلی هستیم باید مسیر ورود ماژول را بروز کنیم.module github.com/mehrdadep/tgomod/v2اکنون نسخه دوم را منتشر می کنیم.$ git add . $ git commit -m &quot:sparkles: New version released&quot $ git tag v2.0.0 $ git push --tagsبروزرسانی به نسخه جدیدبا وجود اینکه نسخه جدیدی از ماژول منتشر شده اما برنامه تست هنوز می تواند بدون مشکل با نسخه قبلی به کار خود ادامه دهد. دستور go get -u نسخه جدید را دریافت نخواهد کرد. برای بروزرسانی پروژه تست به نسخه جدید ماژول باید مسیر ورود را در فایل main.go تغییر دهیم.package main import ( &quotgithub.com/mehrdadep/tgomod&quot tgomodV2 &quotgithub.com/mehrdadep/tgomod/v2&quot ) func main() { tgomod.Print() tgomodV2.Print(&quotVersion 2&quot) }اکنون با اجرای دستورات زیر برنامه تست از هر دو نسخه به صورت همزمان استفاده خواهد کرد.$ go build $ go run main.goبرای مرتب سازی کد و ماژول های استفاده شده در آن می توان از ابزار tidy استفاده کرد. این ابزار ماژول های مورد نیاز برای ساخت پروژه را به آن اضافه می کند.$ go mod tidyاکنون فایل go.mod پروژه تست شامل هر دو نسخه ماژول است.module mod go 1.14 require ( github.com/mehrdadep/tgomod v1.0.0 github.com/mehrdadep/tgomod/v2 v2.0.0 )چگونه با Vendoring کار کنیم؟ماژول های Go به صورت پیشفرض پوشه /vendor را نادیده می گیرند. اما اگر بخواهیم کدهای ماژول های وابسته را به پروژه اضافه کنیم باید از دستور زیر استفاده کنیم.$ go mod vendorاین پوشه همچنین به صورت پیشفرض توسط go build نیز نادیده گرفته می شود. برای تغییر این حالت می توانیم از دستور زیر استفاده کنیم.$ go build -mod=vendorنتیجه گیریماژول ها راهی برای مدیریت نسخه ها و وابستگی ها در زبان Go هستند. استفاده از ماژول ها در حال حاضر تنها روش استاندارد و یکپارچه است که توسط توسعه دهندگان زبان ارائه شده است.استفاده از ماژول ها ضرورت استفاده از GOPATH را از بین برده و به برنامه نویسان این امکان را می دهد که کنترل بیشتری روی پروژه های بزرگ خود داشته باشند.پی نوشت: کدهای مربوط به ماژول مثال در گیت هاب موجود هستند.