Skip to Content

Repository Pattern

Servis va handler kodini SQL bilan aralashtirib yozsangiz, ertaga bazani almashtirish yoki test yozish og’ir bo’ladi. Repository pattern buni hal qiladi: ma’lumotga kirishni bitta interfeys ortiga yashirasiz. Qolgan kod faqat interfeysni biladi, uning ortida PostgreSQL yoki MongoDB turgani — ahamiyatsiz.

Interfeys — shartnoma

Avval model va interfeysni belgilaymiz. Interfeys “nima qilinadi” ni aytadi, “qanday” ni emas.

repository.go
package user import "context" // User — domendagi foydalanuvchi modeli. type User struct { ID string Email string PasswordHash string } // UserRepository foydalanuvchi ma'lumotiga kirish shartnomasi. // Uni PostgreSQL ham, MongoDB ham, xotiradagi fake ham amalga oshira oladi. type UserRepository interface { Create(ctx context.Context, u *User) error ByEmail(ctx context.Context, email string) (*User, error) ByID(ctx context.Context, id string) (*User, error) }

PostgreSQL implementatsiyasi

Endi shu interfeysni pgx bilan amalga oshiramiz. E’tibor bering: bu tur UserRepository ni qanoatlantiradi, lekin servis kodi uni to’g’ridan-to’g’ri emas, interfeys orqali ko’radi.

postgres.go
package user import ( "context" "errors" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" ) type postgresRepository struct { pool *pgxpool.Pool } // NewPostgresRepository UserRepository ni pgx ustida quradi. func NewPostgresRepository(pool *pgxpool.Pool) UserRepository { return &postgresRepository{pool: pool} } func (r *postgresRepository) Create(ctx context.Context, u *User) error { return r.pool.QueryRow(ctx, `INSERT INTO users (email, password_hash) VALUES ($1, $2) RETURNING id`, u.Email, u.PasswordHash).Scan(&u.ID) } func (r *postgresRepository) ByEmail(ctx context.Context, email string) (*User, error) { var u User err := r.pool.QueryRow(ctx, `SELECT id, email, password_hash FROM users WHERE email = $1`, email). Scan(&u.ID, &u.Email, &u.PasswordHash) if errors.Is(err, pgx.ErrNoRows) { return nil, nil // topilmadi — bu xato emas } if err != nil { return nil, err } return &u, nil } func (r *postgresRepository) ByID(ctx context.Context, id string) (*User, error) { var u User err := r.pool.QueryRow(ctx, `SELECT id, email, password_hash FROM users WHERE id = $1`, id). Scan(&u.ID, &u.Email, &u.PasswordHash) if errors.Is(err, pgx.ErrNoRows) { return nil, nil } if err != nil { return nil, err } return &u, nil }

Nega bu kengaytiriladigan — ishlaydigan isbot

Interfeysning kuchi shundaki, uni DB’siz ham amalga oshirsa bo’ladi. Quyidagi to’liq dastur bitta xotiradagi (in-memory) implementatsiyani ishlatadi — servis kodi hech narsa bilmagan holda aynan bir xil ishlaydi. Xuddi shu tarzda ertaga mongoRepository yozsangiz, servis o’zgarmaydi.

main.go
package main import ( "context" "errors" "fmt" ) type User struct { ID, Email, PasswordHash string } type UserRepository interface { Create(ctx context.Context, u *User) error ByEmail(ctx context.Context, email string) (*User, error) } // memRepo — DB o'rniga xotirada saqlaydigan implementatsiya. type memRepo struct { byEmail map[string]*User nextID int } func newMemRepo() *memRepo { return &memRepo{byEmail: map[string]*User{}} } func (r *memRepo) Create(ctx context.Context, u *User) error { if _, ok := r.byEmail[u.Email]; ok { return errors.New("email band") } r.nextID++ u.ID = fmt.Sprintf("u%d", r.nextID) r.byEmail[u.Email] = u return nil } func (r *memRepo) ByEmail(ctx context.Context, email string) (*User, error) { return r.byEmail[email], nil // topilmasa nil } // register faqat interfeysga bog'liq — postgres bo'ladimi, mem bo'ladimi, farqi yo'q. func register(ctx context.Context, repo UserRepository, email string) (*User, error) { u := &User{Email: email, PasswordHash: "hashed"} if err := repo.Create(ctx, u); err != nil { return nil, err } return u, nil } func main() { ctx := context.Background() var repo UserRepository = newMemRepo() // ertaga: NewPostgresRepository(pool) u, err := register(ctx, repo, "aziz@example.uz") if err != nil { panic(err) } fmt.Printf("ro'yxatdan o'tdi: %s (%s)\n", u.Email, u.ID) found, _ := repo.ByEmail(ctx, "aziz@example.uz") fmt.Println("topildi:", found.ID) }
$ go run main.go ro'yxatdan o'tdi: aziz@example.uz (u1) topildi: u1

register funksiyasi postgresRepository bilan ham, memRepo bilan ham o’zgarishsiz ishladi. Bazani almashtirish — yangi tur yozib, main dagi bitta qatorni almashtirish. Bu — repository patternning butun ma’nosi.

Manba / batafsil: go.dev/doc/effective_go#interfaces 

Endi hammasini bitta haqiqiy loyihada birlashtiramiz — Amaliy Loyiha.

Last updated on