طراحی مدل پایگاه داده (Designing a Database Model)
قبل از اینکه بتوانیم از استخر اتصال پایگاه داده استفاده کنیم، باید یک مدل پایگاه داده (Database Model) طراحی کنیم که شامل تمام منطق برای تعامل با پایگاه داده باشد.
در Go، یک الگوی رایج این است که یک لایه دسترسی به داده (Data Access Layer) ایجاد کنیم که شامل تمام کد مربوط به پایگاه داده در یک بسته مستقل باشد.
اگر با اصطلاح مدل (Model) راحت نیستید، میتوانید آن را به عنوان یک لایه سرویس (Service Layer) یا لایه دسترسی به داده (Data Access Layer) در نظر بگیرید. هر اصطلاحی که ترجیح میدهید، ایده این است که ما کد کار با MySQL را در یک پکیج جداگانه از بقیه برنامه کپسوله خواهیم کرد.
فعلاً، ما یک مدل پایگاه داده اسکلتی ایجاد میکنیم و آن را با مقداری داده آزمایشی برمیگردانیم. خیلی کار نخواهد کرد، اما میخواهم قبل از اینکه وارد جزئیات پرسوجوهای SQL شویم، الگو را توضیح دهم.
خوب به نظر میرسد؟ پس بیایید یک دایرکتوری جدید internal/models ایجاد کنیم که حاوی یک فایل snippets.go است:
$ mkdir -p internal/models $ touch internal/models/snippets.go
بیایید فایل internal/models/snippets.go را باز کنیم و یک ساختار Snippet جدید برای نمایش دادههای یک قطعه کد منفرد، به همراه یک نوع SnippetModel با متدهایی برای دسترسی و دستکاری قطعههای کد در پایگاه دادهمان اضافه کنیم. به این صورت:
package models import ( "database/sql" "time" ) // Define a Snippet type to hold the data for an individual snippet. Notice how // the fields of the struct correspond to the fields in our MySQL snippets // table? type Snippet struct { ID int Title string Content string Created time.Time Expires time.Time } // Define a SnippetModel type which wraps a sql.DB connection pool. type SnippetModel struct { DB *sql.DB } // This will insert a new snippet into the database. func (m *SnippetModel) Insert(title string, content string, expires int) (int, error) { return 0, nil } // This will return a specific snippet based on its id. func (m *SnippetModel) Get(id int) (Snippet, error) { return Snippet{}, nil } // This will return the 10 most recently created snippets. func (m *SnippetModel) Latest() ([]Snippet, error) { return nil, nil }
استفاده از SnippetModel (Using the SnippetModel)
برای استفاده از این مدل در هندلرهایمان، نیاز داریم یک ساختار SnippetModel جدید در تابع main() خود ایجاد کنیم و سپس آن را به عنوان یک وابستگی از طریق ساختار application تزریق کنیم - درست مانند کاری که با سایر وابستگیهایمان انجام دادهایم.
به این صورت:
package main import ( "database/sql" "flag" "log/slog" "net/http" "os" // Import the models package that we just created. You need to prefix this with // whatever module path you set up back in chapter 02.01 (Project Setup and Creating // a Module) so that the import statement looks like this: // "{your-module-path}/internal/models". If you can't remember what module path you // used, you can find it at the top of the go.mod file. "snippetbox.letsgofa.net/internal/models" _ "github.com/go-sql-driver/mysql" ) // Add a snippets field to the application struct. This will allow us to // make the SnippetModel object available to our handlers. type application struct { logger *slog.Logger snippets *models.SnippetModel } func main() { addr := flag.String("addr", ":4000", "HTTP network address") dsn := flag.String("dsn", "web:pass@/snippetbox?parseTime=true", "MySQL data source name") flag.Parse() logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) db, err := openDB(*dsn) if err != nil { logger.Error(err.Error()) os.Exit(1) } defer db.Close() // Initialize a models.SnippetModel instance containing the connection pool // and add it to the application dependencies. app := &application{ logger: logger, snippets: &models.SnippetModel{DB: db}, } logger.Info("starting server", "addr", *addr) err = http.ListenAndServe(*addr, app.routes()) logger.Error(err.Error()) os.Exit(1) } ...
اطلاعات تکمیلی (Additional Information)
مزایای این ساختار (Benefits of this Structure)
اگر یک قدم به عقب بردارید، ممکن است بتوانید چند مزیت از راهاندازی پروژه به این روش را ببینید:
جداسازی تمیز دغدغهها (Clean Separation of Concerns) وجود دارد. منطق پایگاه داده (Database Logic) ما به هندلرهایمان متصل نخواهد بود، که به این معنی است که مسئولیتهای هندلر محدود به موارد HTTP است (یعنی اعتبارسنجی درخواستها و نوشتن پاسخها). این در آینده نوشتن تستهای واحد (Unit Tests) متمرکز و دقیق را آسانتر خواهد کرد.
با ایجاد یک نوع
SnippetModelسفارشی و پیادهسازی متدها روی آن، توانستهایم مدل خود را به یک شیء منظم و کپسوله شده (Encapsulated Object) تبدیل کنیم که میتوانیم به راحتی آن را مقداردهی اولیه کرده و سپس به عنوان یک وابستگی (Dependency) به هندلرهایمان منتقل کنیم.از آنجایی که اعمال مدل به عنوان متدهایی روی یک شیء تعریف شدهاند - در مورد ما
SnippetModel- این فرصت وجود دارد که یک رابط (Interface) ایجاد کنیم و آن را برای اهداف تست واحد شبیهسازی (Mock) کنیم.و در نهایت، ما کنترل کامل بر روی اینکه کدام پایگاه داده در زمان اجرا (Runtime) استفاده میشود را داریم، فقط با استفاده از پرچم خط فرمان
-dsn.
واژهنامه اصطلاحات فنی
| اصطلاح فارسی | معادل انگلیسی | توضیح |
|---|---|---|
| مدل پایگاه داده | Database Model | ساختاری که نحوه ذخیرهسازی و دسترسی به دادهها را تعریف میکند |
| لایه دسترسی به داده | Data Access Layer | بخشی از برنامه که مسئول تعامل با پایگاه داده است |
| بسته مستقل | Independent Package | بستهای که میتواند به صورت مستقل از سایر بخشهای برنامه استفاده شود |
| ساختار داده | Data Structure | روشی برای سازماندهی و ذخیره دادهها در برنامه |
| متدهای دسترسی | Access Methods | توابعی که برای خواندن و نوشتن دادهها در پایگاه داده استفاده میشوند |
| منطق کسب و کار | Business Logic | قوانین و عملیاتی که دادهها را پردازش میکنند |
| جداسازی مسئولیتها | Separation of Concerns | اصل طراحی که هر بخش از کد باید وظیفه مشخصی داشته باشد |
| واسط برنامهنویسی | Programming Interface | مجموعهای از متدها برای تعامل با یک بخش از برنامه |
| مدیریت خطا | Error Handling | مکانیزمهای کنترل و پاسخ به خطاها در برنامه |
| کپسولهسازی | Encapsulation | پنهان کردن جزئیات پیادهسازی و ارائه یک رابط ساده |