Нека изградим Pokedex REPL, за да научим за кеша!
Ако не сте запознати с Pokemon или Pokedex, това е добре!

Pokedex е просто измислено устройство, което ни позволява да търсим информация за Pokemon - неща като тяхното име, тип и статистика.

Въпреки че има много функции, но за този блог искам да подчертая изграждането на следното:

  • карта: показва страница с 20 местоположения от света на Pokemon
  • mapb: точно обратното на mapb и показва предишната страница с 20 местоположения от света на Pokemon.

Преглед на високо ниво

  1. Извлечете 20-те резултата от API
  2. Кеширайте резултатите, тъй като командата mapb се нуждае от предишни местоположения и извличането отново би било много скъпо
  3. Изтрийте остарелите записи в кеша след интервал от време „t“.

Кодиране

Нека го разделим на две части:

  1. Изграждане на кеш
  2. Изграждане на командите

1. Изграждане на кеш

Дефиниране на кеша:

type cacheEntry struct {
 createdAt time.Time
 val       []byte
}

type Cache struct {
 cache map[string]cacheEntry
 mux   sync.Mutex
}

Дефиниране на метода за добавяне на запис към кеша:

func (c *Cache) Add(key string, val []byte) {
 c.mux.Lock()
 c.cache[key] = cacheEntry{
  createdAt: time.Now(),
  val:       val,
 }
 c.mux.Unlock()
}

Дефиниране на метода за достъп до запис от кеша:

func (c *Cache) Get(key string) ([]byte, bool) {
 c.mux.Lock()
 res, ok := c.cache[key]
 c.mux.Unlock()
 if !ok {
  return nil, false
 }
 return res.val, true
}

Дефиниране на метода за премахване на остарели записи от кеша след интервал „t“:

func (c *Cache) reap(t time.Duration) {
 c.mux.Lock()
 for k, v := range c.cache {
  if time.Since(v.createdAt) >= t {
   delete(c.cache, k)
  }
 }
 c.mux.Unlock()
}

func (c *Cache) reapLoop(t time.Duration) {
 ticker := time.NewTicker(t)
 for range ticker.C {
  c.reap(t)
 }
}

Накрая конструкторска функция за инстанциране на нашия кеш и едновременно стартиране на нашия цикъл на reap, който ще проверява остарелите записи:

func NewCache(interval time.Duration) *Cache {
 c := Cache{
  cache: make(map[string]cacheEntry),
  mux:   sync.Mutex{},
 }
 go c.reapLoop(interval)
 return &c
}

2. Изграждане на командата: map и mapb

Кодиране на JSON отговора на API със структура:
(Тази стъпка е специфична за езика, тъй като JSON в Go се кодира като структура)

type Location struct {
 LocationID        int    `json:"id"`
 Name              string `json:"name"`
 PokemonEncounters []struct {
  Pokemon struct {
   Name string `json:"name"`
  } `json:"pokemon"`
 } `json:"pokemon_encounters"`
}

Създайте екземпляр на кеша:
(моята кеш функция беше в имена на пакети pokecache)

var cache *pokecache.Cache = pokecache.NewCache(20 * time.Second)

Дефиниране на функция за извличане на заявките и съхраняването им в кеша:

func GetLocation(id string) ([]byte, error) {
 fetch_url := fmt.Sprintf("%s/%s/%v", base_url, location_endpoint, id)
 // finding in cache first
 res, ok := cache.Get(fetch_url)
 // making GET request if not found in cache
 if !ok {
  res, err := http.Get(fetch_url)
  if err != nil {
   return nil, err
  }
  body, err := io.ReadAll(res.Body)
  if res.StatusCode > 299 {
   return nil, fmt.Errorf("response failed with status code: %d and \nbody: %s\n url: %s", res.StatusCode, body, fetch_url)
  }
  if err != nil {
   return nil, err
  }
  cache.Add(fetch_url, body)
  return body, nil
 }
 return res, nil
}

Дефиниране на глобална променлива за съхраняване на текущия идентификатор на местоположение, от който трябва да извлечем:

var LocationID = 1

И накрая нашите функции map и mapb:

func Map() {
 locations := pokeapi.Location{}
 limit := 20
 if pokeapi.LocationID != 1 {
  limit = limit + pokeapi.LocationID - 1
 }
 for ; pokeapi.LocationID <= limit; pokeapi.LocationID++ {
  res, err := pokeapi.GetLocation(pokeapi.LocationID)
  if err != nil {
   fmt.Print(err)
  }
  err = json.Unmarshal(res, &locations)
  if err != nil {
   fmt.Print(err)
  }
  fmt.Println(locations.Name)
 }
}
func Mapb() {
 if pokeapi.LocationID == 1 {
  fmt.Printf("No previous locations to look back\n")
  return
 }
 locations := pokeapi.Location{}
 for i := pokeapi.LocationID - 20; i < pokeapi.LocationID; i++ {
  res, err := pokeapi.GetLocation(i)
  if err != nil {
   fmt.Println(err)
  }
  err = json.Unmarshal(res, &locations)
  if err != nil {
   fmt.Println(err)
  }
  fmt.Println(locations.Name)
 }
 pokeapi.LocationID = pokeapi.LocationID - 20
}

Това е всичко!
Ето връзката към пълния проект: https://github.com/afreen23/pokedex

Благодарим ви, че прочетохте до края. Моля, обмислете следването на писателя и тази публикация. Посетете Stackademic, за да разберете повече за това как демократизираме безплатното обучение по програмиране по света.

Справка: Проектът pokedex на Boot.dev