In‑memory cache for user profiles and their orders

Кешу нужны синхронизация, TTL и изоляция данных от внешних изменений. Один экземпляр передают всем обработчикам через зависимости.

Короткий ответ

Что ответить на собеседовании

Кешу нужны синхронизация, TTL и изоляция данных от внешних изменений. Один экземпляр передают всем обработчикам через зависимости.

Подробный разбор

Ответ с пояснениями

Условие

Реализуйте кеш в памяти, который по заданному ключу возвращает профиль пользователя вместе со списком его заказов.

Требования:

  1. Кеш должен поддерживать TTL (время жизни) для сохранённых записей.
  2. Кеш должен быть доступен из любой функции, обрабатывающей заказы.
  3. Предоставьте сценарии тестирования, демонстрирующие поведение кеша.
package main

import (
    "log"
    "time"
)

type UserProfile struct {
    UUID   string
    Name   string
    Orders []Order
}

type Order struct {
    UUID     string
    CreateAt time.Time
    UpdateAt time.Time
    Basket   interface{}
}

func main() {
    result := 2 + 2
    if result != 4 {
        log.Fatalf("expected 4, got %d", result)
    }

    log.Println("Tests passed")
}

Ответ

Кешу нужны синхронизация, TTL и изоляция данных от внешних изменений. Один экземпляр передают всем обработчикам через зависимости.

Для данного Basket interface{} примем явный контракт: JSON-совместимые данные без сохранения конкретных Go-типов внутри Basket. Сериализация даёт отдельную копию; несовместимые значения возвращают ошибку.

import (
    "encoding/json"
    "errors"
    "sync"
    "time"
)

type profileEntry struct {
    data []byte
    expires time.Time
}
type ProfileCache struct {
    mu sync.Mutex
    entries map[string]profileEntry
}
func (c *ProfileCache) Set(key string, p UserProfile, ttl time.Duration) error {
    if ttl <= 0 { return errors.New("ttl must be positive") }
    data, err := json.Marshal(p)
    if err != nil { return err }
    c.mu.Lock()
    defer c.mu.Unlock()
    if c.entries == nil { c.entries = make(map[string]profileEntry) }
    c.entries[key] = profileEntry{data, time.Now().Add(ttl)}
    return nil
}
func (c *ProfileCache) Get(key string) (UserProfile, bool, error) {
    c.mu.Lock()
    entry, ok := c.entries[key]
    if ok && !time.Now().Before(entry.expires) {
        delete(c.entries, key)
        ok = false
    }
    c.mu.Unlock()
    if !ok { return UserProfile{}, false, nil }
    var p UserProfile
    err := json.Unmarshal(entry.data, &p)
    return p, err == nil, err
}
func (c *ProfileCache) PurgeExpired() {
    c.mu.Lock()
    defer c.mu.Unlock()
    now := time.Now()
    for key, entry := range c.entries {
        if !now.Before(entry.expires) { delete(c.entries, key) }
    }
}

Вызовите PurgeExpired периодически из управляемого жизненного цикла сервиса: иначе непрочитанные просроченные записи останутся в памяти. Для большого кеша добавляют лимит размера. JSON-числа в Basket при таком декодировании становятся float64; если важны точность или конкретный тип, замените interface{} типизированной моделью и явным копированием.

Сценарии проверки: miss; hit до TTL; miss после TTL; обновление продлевает срок; изменение возвращённых Orders не меняет кеш; конкурентные Get/Set; ошибка сериализации Basket. Проверка 2+2 из исходника кеш вообще не проверяет.

ИИ-помощник для собеседований

Хочешь уверенно проходить собеседования?

Попробуй ИИ-помощник для собеседований: слышит вас и собеседника, анализирует экран, подсказывает ответы в реальном времени, работает без VPN и не попадает в захват экрана.

Подробнее