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

ساختار و سازماندهی پروژه

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

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

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

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

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

$ 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 مانند فایل‌های UI، makefile‌ها و تعاریف ماژول (شامل فایل go.mod ما) را نگه دارد.

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

بازسازی کد موجود شما

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

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

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

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

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