مدیریت وابستگی ها در زبان 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 را از بین برده و به برنامه نویسان این امکان را می دهد که کنترل بیشتری روی پروژه های بزرگ خود داشته باشند.


پی نوشت: کدهای مربوط به ماژول مثال در گیت هاب موجود هستند.