CodeBlanks Logo Icon CodeBlanks

Database layer

Write the data access layer with database/sql.

In-memory repository

Writing an in-memory repository first, instead of a database, lets you try the service right away. Because the HTTP server handles requests concurrently, the data is protected with a sync.Mutex:

type memRepo struct {
    mu     sync.Mutex
    urunler []Urun
}

func (r *memRepo) Ekle(u Urun) error {
    r.mu.Lock()
    defer r.mu.Unlock()
    r.urunler = append(r.urunler, u)
    return nil
}

Moving to a database

When you write a second type implementing the same Repo interface with database/sql, the service code doesn't change:

type sqlRepo struct{ db *sql.DB }

func (r sqlRepo) Ekle(u Urun) error {
    _, err := r.db.Exec("INSERT INTO urunler(ad, fiyat) VALUES(?, ?)", u.Ad, u.Fiyat)
    return err
}

main.go chooses which implementation to use; this is called dependency injection.

Practice Memory repository

Write Urun and memRepo (sync.Mutex + []Urun). The Ekle and Liste methods work under the lock. Add two products and print len(r.Liste()).

Starter code

main.go
package main

import (
    "fmt"
    "sync"
)

func main() {
    // buraya yaz
}

Sample solution

main.go
package main

import (
    "fmt"
    "sync"
)

type Urun struct {
    Ad    string
    Fiyat float64
}

type memRepo struct {
    mu      sync.Mutex
    urunler []Urun
}

func (r *memRepo) Ekle(u Urun) error {
    r.mu.Lock()
    defer r.mu.Unlock()
    r.urunler = append(r.urunler, u)
    return nil
}

func (r *memRepo) Liste() []Urun {
    r.mu.Lock()
    defer r.mu.Unlock()
    return append([]Urun(nil), r.urunler...)
}

func main() {
    r := &memRepo{}
    r.Ekle(Urun{"Kalem", 10})
    r.Ekle(Urun{"Defter", 25})
    fmt.Println(len(r.Liste()))
}

Expected output

2

Questions and answers

Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.

1 Why do memRepo methods use a pointer receiver (<code>*memRepo</code>)?

  • For JSON
  • So the mutex and slice aren't copied and changes stay on the same object Correct answer
  • Because it's shorter
  • Because interfaces don't support value receivers

Explanation: A value receiver copies on each call; the added product stays in the copy and a copied mutex protects nothing.

2 Release the lock

Question
r.mu.Lock()
____ r.mu.Unlock()
return append([]Urun(nil), r.urunler...)
Answer
r.mu.Lock()
defer r.mu.Unlock()
return append([]Urun(nil), r.urunler...)

Explanation: defer Unlock guarantees the lock is released however the function ends.

3 Repository implementations

Term Meaning
memRepo In memory, for tests
sqlRepo With database/sql
Repo Common interface
sync.Mutex Guards concurrent access

All lessons in Advanced