04 May 2025·5 dk okuma

Temiz bir Go veri katmanı için Go generics CRUD repository deseni

Listeleme/get/oluşturma/güncelleme/silme mantığını okunaklı kısıtlarla, reflection kullanmadan ve temiz kodla yeniden kullanmanıza olanak veren pratik bir Go generics CRUD repository desenini öğrenin.

Temiz bir Go veri katmanı için Go generics CRUD repository deseni

Go'da CRUD repository'leri neden karışır

CRUD repository'leri başlangıçta basittir. Önce GetUser yazar, sonra ListUsers, sonra aynı şeyi Orders için, sonra Invoices için yazarsınız. Birkaç varlıktan sonra veri katmanı neredeyse kopyalardan oluşan bir yığına dönüşür ve küçük farkları gözden kaçırmak kolaylaşır.

En sık tekrarlanan şey çoğunlukla SQL değil. Sorguyu çalıştırma, satırları tarama, “bulunamadı”yı ele alma, veritabanı hatalarını eşleme, sayfalama varsayımlarını uygulama ve girdileri doğru tiplere dönüştürme gibi çevresel akış tekrar eder.

Yaygın zorluklar tanıdıktır: kopyalanmış Scan kodu, tekrar eden context.Context ve transaction desenleri, boilerplate LIMIT/OFFSET ele alımı (bazen toplam sayılarla), aynı “0 satır = bulunamadı” kontrolü ve kopyalanmış INSERT ... RETURNING id varyasyonları.

Tekrarlar can sıkmaya başladığında birçok ekip reflection'a yönelir. Reflection “birkez yaz” CRUD sözü verir: herhangi bir struct'ı al ve çalışma zamanında sütunlardan doldur. Ancak maliyet sonradan çıkar. Reflection ağırlıklı kod okunması daha zor olur, IDE desteği zayıflar ve hatalar derleme zamanından çalışma zamanına kayar. Alan yeniden adlandırma veya nullable bir sütun ekleme gibi küçük değişiklikler sadece testlerde ya da production'da sürpriz olur.

Tip-güvenli yeniden kullanım, CRUD akışını paylaşmak anlamına gelir ama Go'nun günlük rahatlıklarından vazgeçmeme anlamına da gelir: net imzalar, derleyici ile kontrol edilen türler ve gerçekten yardımcı olan autocomplete. Generics ile Get[T] ve List[T] gibi işlemleri yeniden kullanabilir, fakat her varlığın tahmin edilemeyen kısımlarını (örneğin bir satırı T'ye nasıl tarayacağınız) sağlamasını zorunlu kılabilirsiniz.

Bu desen kasıtlı olarak veri erişim katmanı ile ilgilidir. SQL ve eşlemeyi tutarlı ve sıkıcı kılar. Domain'inizi modellemeye, iş kurallarını uygulamaya veya servis seviyesindeki mantığı değiştirmeye çalışmaz.

Tasarım hedefleri (ve neyi çözmeye çalışmayacağı)

İyi bir repository deseni günlük veritabanı erişimini öngörülebilir hale getirir. Bir repository'i okuyabilmeli, ne yaptığını, hangi SQL'i çalıştırdığını ve hangi hataları dönebileceğini hızlıca görebilmelisiniz.

Hedefler basittir:

  • Uçtan uca tip güvenliği (ID'ler, varlıklar ve sonuçlar any olmasın)
  • Niyetinizi açıklayan, tip jimnastiği yapmayan kısıtlar
  • Önemli davranışı gizlemeden daha az boilerplate
  • List/Get/Create/Update/Delete arasında tutarlı davranış

Aynı derecede önemli olan non-goal'lar da var. Bu bir ORM değil. Alan eşlemelerini tahmin etmemeli, tabloları otomatik birleştirmemeli veya sorguları sessizce değiştirmemeli. “Sihirli eşleme” sizi tekrar reflection, tag'ler ve kenar durumlarına geri iter.

Normal bir SQL akışını varsayın: açık SQL (veya ince bir query builder), net transaction sınırları ve üzerinde düşünebileceğiniz hatalar. Bir şey başarısız olduğunda hata size “bulunamadı”, “çakışma/kısıtlama ihlali” veya “DB ulaşılamıyor” demeli; belirsiz bir “repository hatası” değil.

Ana karar, neyin generic olacağı ve neyin varlığa özel kalacağıdır.

  • Generic: akış (sorguyu çalıştır, tara, tiplenmiş değerleri döndür, yaygın hataları çevir).
  • Varlığa özel: anlam (tablo isimleri, seçilen sütunlar, join'ler ve SQL stringleri).

Tüm varlıkları tek evrensel bir filtre sistemine zorlamak genellikle kodu okumayı zorlaştırır; iki net sorgu yazmaktan daha kötü olabilir.

Varlık ve ID kısıtlarını seçmek

Çoğu CRUD kodu tekrar eder çünkü her tablo aynı temel hareketleri yapar, ama her varlığın kendi alanları vardır. Generics ile hile, küçük bir şekli paylaşmak ve geri kalan her şeyi serbest bırakmaktır.

Repo'nun bir varlık hakkında gerçekten bilmesi gereken şeyi belirleyin. Birçok ekip için tek evrensel parça ID'dir. Zaman damgaları faydalı olabilir ama evrensel değildir; her tipe zorlamak modeli yapay hissettirebilir.

Yaşayabileceğiniz bir ID tipi seçin

ID tipiniz veritabanında satırları nasıl tanımladığınıza uymalıdır. Bazı projeler int64, bazıları UUID string kullanır. Tüm servisler arasında çalışan bir yaklaşım isterseniz ID'yi generic yapın. Tüm kod tabanınız tek bir ID tipi kullanıyorsa, sabit tutmak imzaları kısaltabilir.

ID'ler için iyi bir varsayılan kısıtlama comparable'dır; çünkü ID'leri karşılaştırır, map anahtarı olarak kullanır ve etrafta taşırız.

type ID interface {
	comparable
}

type Entity[IDT ID] interface {
	GetID() IDT
	SetID(IDT)
}

Varlık kısıtlarını minimal tutun

Struct embedding veya ~struct{...} gibi type-set numaralarıyla alan zorlamaktan kaçının. Güçlü görünseler de domain tiplerinizi repository desenine bağlarlar.

Bunun yerine, paylaşılan CRUD akışının gerçekten ihtiyaç duyduğu kadarını talep edin:

  • ID'yi alıp ayarlama (böylece Create ID'yi döndürebilir ve Update/Delete hedef alabilir)

Daha sonra soft delete veya optimistic locking gibi özellikler ekliyorsanız, küçük opt-in arayüzleri ekleyin (örneğin GetVersion/SetVersion) ve yalnızca gerektiğinde kullanın. Küçük arayüzler genellikle iyi yaşlanır.

Okunabilir kalan generic repository arayüzü

Bir repository arayüzü uygulamanızın ihtiyaç duyduğu şeyi tarif etmeli, veritabanının yaptığı şeyi değil. Eğer arayüz SQL gibi hissediyorsa, detaylar her yere sızar.

Metot setini küçük ve öngörülebilir tutun. context.Context'i ilk parametre yapın, sonra birincil girdi (ID veya veri), sonra opsiyonel düğmeler (flags) bir struct içinde toplanmış olsun.

type Repository[T any, ID comparable, CreateIn any, UpdateIn any, ListQ any] interface {
	Get(ctx context.Context, id ID) (T, error)
	List(ctx context.Context, q ListQ) ([]T, error)
	Create(ctx context.Context, in CreateIn) (T, error)
	Update(ctx context.Context, id ID, in UpdateIn) (T, error)
	Delete(ctx context.Context, id ID) error
}

List için evrensel bir filtre tipi zorlamaktan kaçının. Filtreler varlıkların en çok farklılaştığı yerdir. Pratik bir yaklaşım varlığa özel sorgu tipleri artı gömülebilen küçük bir ortak sayfalama şeklidir.

type Page struct {
	Limit  int
	Offset int
}

Hata ele alma genellikle repository'leri gürültülü yapar. Çağıranların hangi hatalara göre dallanabileceğine önceden karar verin. Basit bir set genellikle yeterlidir:

  • Bir ID yoksa ErrNotFound
  • Benzersiz ihlaller veya versiyon çatışmaları için ErrConflict
  • Girdi geçersizse ErrValidation (sadece repo doğrulama yapıyorsa)

Diğer her şey altta yatan hatayla sarılmış düşük seviye hata olsun (DB/ağ). Bu sözleşmeyle servis kodu bugün PostgreSQL olsa da başka bir şeye geçse de “not found” veya “conflict” ile ilgilenebilir.

Reflection olmadan yineleme nasıl sağlanır

From schema to full app
Design PostgreSQL data, then generate web and mobile apps on top of it.
Start Building

Reflection genellikle “herhangi bir struct'ı doldur” istediğinizde sızar. Bu, hataları çalışma zamanına saklar ve kuralları belirsizleştirir.

Daha temiz bir yaklaşım, yalnızca sıkıcı parçaları yeniden kullanmaktır: sorguları çalıştırmak, satırlarda dolaşmak, etkilenen satır sayısını kontrol etmek ve hataları tutarlı şekilde sarmak. Struct'lara dönüşümü açık tutun.

Sorumlulukları ayırın: SQL, eşleme, paylaşılan akış

Pratik bir ayırma şöyle görünebilir:

  • Varlığa özel: SQL stringleri ve parametre sırası tek bir yerde olsun
  • Varlığa özel: küçük mapping fonksiyonları yazın; satırları somut struct'a tarasın
  • Generic: sorguyu çalıştıran ve mapper'ı çağıran paylaşılan akış

Böylece generics, tekrarı azaltır ama veritabanının ne yaptığını gizlemez.

Aşağıda *sql.DB veya *sql.Tx geçebileceğiniz küçük bir soyutlama örneği var:

type DBTX interface {
	ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
	QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
	QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
}

Generics ne yapmalı (ve yapmamalı)

Generic katman struct'ınızı “anlamaya” çalışmamalıdır. Bunun yerine sizden açıkça verilen fonksiyonları kabul etmelidir, örneğin:

  • girdileri sorgu argümanlarına çeviren bir binder
  • sütunları bir varlığa tarayan bir scanner

Örneğin, bir Customer repository SQL'i sabitler (selectByID, insert, update) ve scanCustomer(rows)'ı bir kez uygular. Generic List döngüyü, context'i ve hata sarmasını hallederken scanCustomer eşlemeyi tip-güvenli ve açık tutar.

Bir sütun eklerseniz SQL'i ve scanner'ı güncellersiniz. Derleyici neyin bozulduğunu bulmanıza yardımcı olur.

Adım adım: desenin uygulanması

Amaç, List/Get/Create/Update/Delete için tekrar kullanılabilir tek bir akış sağlamak ama her repository'i SQL ve satır eşlemesi konusunda dürüst tutmaktır.

1) Temel tipleri tanımlayın

En az kısıtlama ile başlayın. Kod tabanınız için çalışan bir ID tipi seçin ve tahmin edilebilir bir repository arayüzü belirleyin.

type ID interface{ ~int64 | ~string }

type Repo[E any, K ID] interface {
	Get(ctx context.Context, id K) (E, error)
	List(ctx context.Context, limit, offset int) ([]E, error)
	Create(ctx context.Context, e *E) error
	Update(ctx context.Context, e *E) error
	Delete(ctx context.Context, id K) error
}

2) DB ve transaction için bir executor ekleyin

Generic kodu doğrudan *sql.DB veya *sql.Tx'ye bağlamayın. Çağırdığınız küçük bir executor arayüzüne dayanarak hem DB hem de transaction ile aynı kodu kullanın.

3) Paylaşılan akış ile bir generic base oluşturun

Executor ve birkaç fonksiyon alanını tutan baseRepo[E,K] oluşturun. Base, sıkıcı parçaları halleder: sorguyu çağırmak, “bulunamadı”yı eşlemek, etkilenen satırları kontrol etmek ve tutarlı hatalar döndürmek.

4) Varlığa özel parçaları uygulayın

Her varlık repository'si generic olmayan şu şeyleri sağlar:

  • list/get/create/update/delete için SQL
  • bir scan(row) fonksiyonu
  • bir bind(...) fonksiyonu ile sorgu argümanlarını döndürme

5) Somut repository'leri servislerde kullanıma bağlayın

NewCustomerRepo(exec Executor) *CustomerRepo oluşturun; bu baseRepo'yu embed edebilir veya sarmalayabilir. Servis katmanı Repo[E,K] arayüzüne bağlı olsun ve ne zaman transaction başlatılacağına karar versin; repository kendisine verilen executor'ü kullanır.

Sürpriz olmadan List/Get/Create/Update/Delete yönetimi

Make logic explicit
Put business rules in the Business Process Editor instead of scattering checks across repos.
Build Logic

Generic repository yalnızca her metod her yerde aynı şekilde davranıyorsa yardımcı olur. Çoğu sorun küçük tutarsızlıklardan gelir: bir repo created_at'a göre sıralarken diğeri id'ye göre sıralar; biri eksik kayıt için nil, nil döndürür, diğeri hata.

List: kaymayan sayfalama ve sıralama

Tek bir sayfalama stilini seçin ve tutarlı uygulayın. Offset sayfalama (limit/offset) basit ve admin ekranları için uygundur. Cursor sayfalama sonsuz kaydırma için daha iyidir ama stabil bir sıralama anahtarı gerekir.

Ne seçerseniz seçin, sıralamayı açık ve stabil tutun. Benzersiz bir sütuna göre sıralama (genellikle PK) yeni satırlar eklendikçe öğelerin sayfalar arasında kaymasını önler.

Get: net bir “bulunamadı” sinyali

Get(ctx, id) tiplenmiş bir varlık ve net bir eksik-kayıt sinyali (genellikle ErrNotFound) döndürmelidir. Sıfır-değer varlık + nil hata döndürmekten kaçının; çağıranlar “kayıt yok” ile “alanlar boş”u ayırt edemez.

Tip veri için, hata durum içindir. Erken bu alışkanlığı edinin.

İmplementasyona başlamadan önce bir kaç karar verin ve bunları tutarlı tutun:

  • Create: ID ve zaman damgaları olmayan bir giriş tipi mi kabul ediyorsunuz yoksa tam bir varlık mı? Birçok ekip Create(ctx, in CreateX)'i tercih eder; böylece çağıranların sunucuya ait alanları ayarlaması engellenir.
  • Update: tam yerini alacak şekilde mi yoksa patch mi? Patch ise, sıfır değerlerin belirsiz olduğu düz struct'lar kullanmayın. Pointerlar, nullable tipler veya açık bir field mask kullanın.
  • Delete: hard delete mi yoksa soft delete mi? Soft delete ise Get varsayılan olarak silinmiş satırları gizler mi kararlaştırın.

Ayrıca yazma metodlarının ne döndürdüğüne karar verin. Sürprizleri azaltmak için güncellenmiş varlığı döndürmek (DB varsayımlarıyla) veya sadece ID ile ErrNotFound döndürmek gibi seçenekler tercih edilir.

Generic ve varlığa özel parçalar için test stratejisi

Prototype admin tools faster
Build internal tools with UI, auth, and business logic without writing endless repository code.
Create Tool

Bu yaklaşımdan fayda görmek istiyorsanız güvenilir olması gerekir. Testleri kodla aynı şekilde ayırın: paylaşılan yardımcıları bir kez, her varlığın SQL ve scanning'ini ayrı ayrı test edin.

Paylaşılan parçaları mümkün olduğunca saf küçük fonksiyonlar olarak ele alın: sayfalama doğrulama, sıralama anahtarlarını izin verilen sütunlara eşleme, WHERE parçaları oluşturma gibi. Bunlar hızlı birim testleriyle kapsanabilir.

List sorguları için table-driven testler işe yarar: boş filtreler, bilinmeyen sort anahtarları, limit 0, limit'in max'ı aşması, negatif offset ve “bir sonraki sayfa” sınırları gibi kenar durumları test edin.

Varlığa özel testleri ise beklenen SQL'in çalıştırıldığını ve satırların varlık tipine doğru tarandığını kontrol edecek şekilde yazın. SQL mock veya hafif bir test veritabanı kullanın ve scanner mantığının null'ları, opsiyonel sütunları ve tip dönüşümlerini doğru ele aldığından emin olun.

Desen transaction destekliyorsa commit/rollback davranışını küçük bir fake executor ile test edin:

  • Begin tx-odaklı bir executor döndürür
  • hata durumunda rollback tam olarak bir kez çağrılır
  • başarı durumunda commit tam olarak bir kez çağrılır
  • commit başarısız olursa hata olduğu gibi döner

Ayrıca her repository'nin geçmesi gereken küçük “sözleşme testleri” ekleyin: create ardından get aynı veriyi döndürür, update beklenen alanları değiştirir, delete sonra get ErrNotFound döndürür ve list sıralama aynı girişlerde stabil kalır.

Yaygın hatalar ve tuzaklar

Generics, her şeyi tek bir repository'de toplamak için cazip gelebilir. Veri erişimi küçük ama önemli farklılıklara doludur ve bu farklar önemlidir.

Sık görülen tuzaklar:

  • Her metodun devasa bir opsiyon çantası aldığı kadar aşırı genelleştirme (join'ler, arama, izinler, soft delete, caching). Bu noktada ikinci bir ORM inşa etmiş olursunuz.
  • Çok akıllı kısıtlamalar. Okuyucuların bir varlığın ne yapması gerektiğini anlamak için type set'leri çözmesi gerekiyorsa, soyutlama sağladığından pahalı hale gelir.
  • Girdi tiplerini DB modeli olarak görmek. Create ve Update aynı struct'ı alırsa DB detayları handler'lara ve testlere sızar ve şema değişiklikleri uygulama çapında dalga halinde yayılır.
  • List içinde sessiz davranışlar: stabil olmayan sıralama, tutarsız varsayımlar veya varlığa göre değişen paging kuralları.
  • Bulunamadı durumunu çağıranların hata metinlerini parse etmesine zorlamak yerine errors.Is kullanmayı engellemek.

Somut bir örnek: ListCustomers ORDER BY koymadığı için her seferinde farklı sırada customer döndürüyor. Bu da sayfalama sırasında kopyalama veya atlama yaratır. Varsayılan sıralamayı açıkça koyun (örneğin primary key) ve bunu test edin.

Benimsemeden önce hızlı bir kontrol listesi

Deploy where you run
Deploy to AppMaster Cloud or your preferred cloud provider without reworking your backend.
Deploy App

Generic repository'yi her pakete dağıtmadan önce, tekrarları ortadan kaldırıp önemli veritabanı davranışını gizlemediğinden emin olun.

Tutarlılıkla başlayın. Bir repo context.Context alırken diğeri almıyorsa, veya biri (T, error) döndürürken diğeri (*T, error) döndürüyorsa, acı her yerde görünür: servislerde, testlerde ve mock'larda.

Her varlığın SQL'in için hâlâ tek bir açık yeri olsun. Generics akışı (tara, doğrula, hataları eşle) yeniden kullansın; sorguları parçalar halinde dağıtmasın.

Hata ihtimalini azaltan kontroller:

  • List/Get/Create/Update/Delete için tek bir imza konvansiyonu
  • Her repoda kullanılan tek bir bulunamadı kuralı
  • Dokümante edilmiş ve test edilmiş stabil list sıralaması
  • *sql.DB ve *sql.Tx üzerinde aynı kodu çalıştırmanın temiz yolu (executor arayüzü aracılığıyla)
  • Generic kod ile varlık kuralları (doğrulama ve iş kontrolleri) arasında net sınır

Eğer AppMaster ile dahili araçlar hızlıca inşa edip sonra üretilen Go kodunu dışarı aktarıp genişletiyorsanız, bu kontroller veri katmanını öngörülebilir ve test edilebilir tutar.

Gerçekçi bir örnek: Customer repository inşa etmek

Tip güvenli kalırken karmaşıklaşmayan küçük bir Customer repository yapısı:

Önce saklanan modeli tanımlayın. ID'yi güçlü tipli tutun ki yanlışlıkla karışmasın:

type CustomerID int64

type Customer struct {
	ID     CustomerID
	Name   string
	Status string // "active", "blocked", "trial"...
}

API'nin kabul ettiği şeyi sakladığınız şeyden ayırın. Create ve Update burada farklı olmalı.

type CreateCustomerInput struct {
	Name   string
	Status string
}

type UpdateCustomerInput struct {
	Name   *string
	Status *string
}

Generic base paylaşılan akışı halleder (SQL çalıştırma, tarama, hata eşleme), Customer repo ise Customer'a özgü SQL ve mapping'e sahiptir. Servis katmanından bakınca arayüz temiz kalır:

type CustomerRepo interface {
	Create(ctx context.Context, in CreateCustomerInput) (Customer, error)
	Update(ctx context.Context, id CustomerID, in UpdateCustomerInput) (Customer, error)
	Get(ctx context.Context, id CustomerID) (Customer, error)
	Delete(ctx context.Context, id CustomerID) error
	List(ctx context.Context, q CustomerListQuery) ([]Customer, int, error)
}

List için filtreleri ve sayfalamayı bir istek objesi olarak ele alın. Bu çağrı yerlerini okunur tutar ve limit unutmayı zorlaştırır.

type CustomerListQuery struct {
	Status *string // filter
	Search *string // name contains
	Limit  int
	Offset int
}

Buradan desen iyi ölçeklenir: bir sonraki varlık için yapıyı kopyalayın, inputları depolanan modellerden ayırın ve scanning'i açık tutun; böylece değişiklikler görünür ve derleyici yardımcı olur.

SSS

Go'da generic CRUD repository'leri gerçekte hangi problemi çözüyor?

Generics'i, SQL sorgusu yürütme, satır döngüsü, bulunamadı işleme, sayfalama varsayımları ve hata eşlemesi gibi akışı yeniden kullanmak için kullanın; SQL ve satır eşlemesini her varlık için açık tutun. Bu, veri katmanınızı çalışma zamanında gizlenen “sihirli” hale getirmeden tekrarları azaltır.

Neden reflection tabanlı “herhangi bir struct'ı tara” CRUD yardımcılarından kaçınmalıyım?

Reflection, eşleme kurallarını gizler ve hataları çalışma zamanına kaydırır. Derleyici kontrollerini kaybedersiniz, IDE desteği zayıflar ve küçük şema değişiklikleri sürprizlere yol açar. Generics ile birlikte açık scanner fonksiyonları kullanmak, tip güvenliğini korurken tekrarı paylaşmanıza izin verir.

ID tipi için mantıklı bir kısıtlama nedir?

İyi bir varsayılan comparable'dır, çünkü ID'ler karşılaştırılır, map anahtarı olarak kullanılır ve her yerde taşınır. Sisteminizde hem int64 hem de UUID string gibi farklı ID stilleri varsa, ID tipini generic yapmak tüm repository'lerde tek bir seçimi zorlamaz.

Varlık (entity) kısıtlaması neleri içermeli (ve neleri içermemeli)?

Minimumda tutun: genelde paylaşılan CRUD akışının ihtiyaç duyduğu kadar — GetID() ve SetID() gibi. Gömme veya karmaşık type-set numaralarıyla alanları zorlamayın; bunlar domain tiplerinizi repository deseniyle gereksiz yere bağlar ve yeniden düzenlemeyi zorlaştırır.

Hem *sql.DB hem de *sql.Tx'yi nasıl temiz desteklerim?

Kısaltılmış bir executor arayüzü (DBTX gibi) kullanın; yalnızca kullandığınız yöntemleri içersin: QueryContext, QueryRowContext, ExecContext. Böylece repo kodunuz *sql.DB veya *sql.Tx ile çalışabilir, kodda dallanma/çoğaltma gerekmez.

Get için “bulunamadı”yı en iyi nasıl bildiririm?

Eksik kayıt için net bir sinyal döndürün: sıfır değer + nil hata yerine paylaşılan bir sentinel ErrNotFound kullanın. Bu şekilde servis kodu errors.Is ile güvenle dallanabilir; eksik ile boş alan arasındaki fark belirsiz olmaz.

Create/Update tam varlık struct'ını mı almalı?

Girişleri depolanan modelden ayırın. Create(ctx, CreateInput) ve Update(ctx, id, UpdateInput) tercih edin, böylece çağıranlar ID veya sunucu tarafından yönetilen timestamp gibi alanları ayarlayamaz. Patch güncellemeleri için pointerlar veya nullable tipler kullanın; aksi halde sıfır değerleri ayırt etmek zorlaşır.

List sayfalama tutarsız sonuçlar döndürmesini nasıl engellerim?

Her zaman açık ve stabil bir ORDER BY belirleyin, tercihen birincil anahtar gibi benzersiz bir sütuna göre. Bunu yapmazsanız, yeni satırlar eklendikçe sayfalar arasında atlama veya çoğaltma yaşanabilir.

Repository'lerin servislere hangi hata sözleşmesini sağlaması gerekir?

Servislerin dallanabileceği küçük bir hata seti sunun: örneğin ErrNotFound ve ErrConflict. Diğer tüm hataları altta yatan DB hatası bağlamıyla sarın. Çağıranların hata metinlerini parse etmesini gerektirmeyin; errors.Is kontrolü hedef olsun ve loglar için açıklayıcı mesaj verin.

Generic repository desenini nasıl fazla test etmeksizin güvenilir hale getiririm?

Paylaşılan yardımcıları bir kez test edin (sayfalama normalizasyonu, bulunamadı eşlemesi, etkilenen-satır kontrolleri) ve sonra her varlığın SQL ve scanning'ini ayrı testlerle kapsayın. Ayrıca her repository için küçük “sözleşme testleri” ekleyin: create-then-get eşleşir, update beklenen alanları değiştirir, delete sonra get ErrNotFound döndürür ve list sıralaması stabil olmalıdır.

Başlaması kolay
Harika bir şey yaratın

Ücretsiz planla AppMaster ile denemeler yapın.
Hazır olduğunuzda uygun aboneliği seçebilirsiniz.

Başlayın