Let's Go مبانی › ساختار و سازماندهی پروژه
قبلی · فهرست · بعدی
فصل 2.7.

ساختار و سازماندهی پروژه (Project Structure and Organization)

قبل از اینکه کد بیشتری به فایل main.go اضافه کنیم، زمان خوبی است که به نحوه سازماندهی و ساختاردهی این پروژه فکر کنیم.

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

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

برای این پروژه، ما یک ساختار کلی را پیاده‌سازی خواهیم کرد که از یک رویکرد محبوب و آزمایش‌شده (Popular and Proven Approach) پیروی می‌کند. این یک نقطه شروع محکم است و شما باید بتوانید ساختار کلی را در طیف وسیعی از پروژه‌ها مجدداً استفاده کنید.

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

$ cd $HOME/code/snippetbox
$ rm main.go
$ mkdir -p cmd/web internal ui/html ui/static
$ touch cmd/web/main.go
$ touch cmd/web/handlers.go

ساختار مخزن پروژه شما اکنون باید به این صورت باشد:

02.07-01.png

بیایید لحظه‌ای وقت بگذاریم تا در مورد اینکه هر یک از این دایرکتوری‌ها برای چه استفاده می‌شوند صحبت کنیم.

پس چرا از این ساختار استفاده می‌کنیم؟

دو مزیت بزرگ وجود دارد:

  1. این یک جداسازی تمیز بین دارایی‌های Go و غیر Go ایجاد می‌کند. تمام کد Go که می‌نویسیم به طور انحصاری در دایرکتوری‌های cmd و internal قرار خواهد گرفت و ریشه پروژه را آزاد می‌گذارد تا دارایی‌های غیر Go مانند فایل‌های رابط کاربری، فایل‌های ساخت و تعریف ماژول‌ها (از جمله فایل go.mod ما) را نگه دارد.

  2. این به خوبی مقیاس‌پذیر است اگر بخواهید یک برنامه اجرایی دیگر به پروژه خود اضافه کنید. به عنوان مثال، ممکن است بخواهید یک CLI (رابط خط فرمان) برای خودکارسازی برخی از وظایف مدیریتی در آینده اضافه کنید. با این ساختار، می‌توانید این برنامه CLI را در زیر cmd/cli ایجاد کنید و قادر خواهید بود تمام کدی که در زیر دایرکتوری internal نوشته‌اید را وارد و مجدداً استفاده کنید.

بازسازی کد موجود شما (Refactoring Your Existing Code)

بیایید به سرعت کدی که قبلاً نوشته‌ایم را به این ساختار جدید منتقل کنیم.

File: cmd/web/main.go
package main

import (
    "log"
    "net/http"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("GET /{$}", home)
    mux.HandleFunc("GET /snippet/view/{id}", snippetView)
    mux.HandleFunc("GET /snippet/create", snippetCreate)
    mux.HandleFunc("POST /snippet/create", snippetCreatePost)

    log.Print("starting server on :4000")
    
    err := http.ListenAndServe(":4000", mux)
    log.Fatal(err)
}
File: cmd/web/handlers.go
package main

import (
    "fmt"
    "net/http"
    "strconv"
)

func home(w http.ResponseWriter, r *http.Request) {
    w.Header().Add("Server", "Go")
    w.Write([]byte("Hello from Snippetbox"))
}

func snippetView(w http.ResponseWriter, r *http.Request) {
    id, err := strconv.Atoi(r.PathValue("id"))
    if err != nil || id < 1 {
        http.NotFound(w, r)
        return
    }

    fmt.Fprintf(w, "Display a specific snippet with ID %d...", id)
}

func snippetCreate(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Display a form for creating a new snippet..."))
}

func snippetCreatePost(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusCreated)
    w.Write([]byte("Save a new snippet..."))
}

بنابراین اکنون برنامه وب ما شامل چندین فایل .go در زیر دایرکتوری cmd/web است. برای اجرای این‌ها، می‌توانیم از دستور go run به این صورت استفاده کنیم:

$ cd $HOME/code/snippetbox
$ go run ./cmd/web
2024/03/18 11:29:23 starting server on :4000

اطلاعات اضافی

دایرکتوری internal (The Internal Directory)

مهم است که اشاره کنیم که نام دایرکتوری internal در Go معنای خاص و رفتار خاصی دارد: هر بسته‌ای که در زیر این دایرکتوری قرار دارد فقط می‌تواند توسط کد داخل والد دایرکتوری internal وارد شود. در مورد ما، این به این معناست که هر بسته‌ای که در internal قرار دارد فقط می‌تواند توسط کد داخل دایرکتوری پروژه snippetbox وارد شود.

یا، از طرف دیگر، این به این معناست که هر بسته‌ای که در internal قرار دارد نمی‌تواند توسط کد خارج از پروژه ما وارد شود.

این مفید است زیرا از وارد کردن و وابستگی دیگر کدها به بسته‌های (احتمالاً بدون نسخه و پشتیبانی نشده) در دایرکتوری internal جلوگیری می‌کند — حتی اگر کد پروژه به صورت عمومی در جایی مانند GitHub در دسترس باشد.

واژه‌نامه اصطلاحات فنی

اصطلاح فارسی معادل انگلیسی توضیح
ساختار و سازماندهی پروژه Project Structure and Organization نحوه چیدمان و سازماندهی فایل‌ها و دایرکتوری‌های یک پروژه نرم‌افزاری
کد خاص برنامه Application-specific Code کدی که مختص به یک برنامه خاص است و قابل استفاده مجدد در برنامه‌های دیگر نیست
کد غیر خاص برنامه Non-application-specific Code کدی که می‌تواند در چندین برنامه مورد استفاده مجدد قرار گیرد
دارایی‌های رابط کاربری UI Assets فایل‌هایی که برای ساخت رابط کاربری استفاده می‌شوند، مانند قالب‌های HTML و فایل‌های CSS
فایل‌های استاتیک Static Files فایل‌هایی که بدون تغییر به کاربر ارسال می‌شوند، مانند تصاویر و فایل‌های CSS
رویکرد محبوب و آزمایش‌شده Popular and Proven Approach روشی که توسط بسیاری از توسعه‌دهندگان استفاده شده و کارایی آن ثابت شده است
بازسازی کد Code Refactoring تغییر ساختار کد بدون تغییر در عملکرد آن
برنامه اجرایی Executable Program برنامه‌ای که می‌تواند به طور مستقیم توسط سیستم عامل اجرا شود
کد قابل استفاده مجدد Reusable Code کدی که می‌تواند در بخش‌های مختلف یک برنامه یا در برنامه‌های مختلف استفاده شود
مقیاس‌پذیری Scalability توانایی یک ساختار برای رشد و گسترش بدون نیاز به تغییرات اساسی