GoDasturchi
Idempotency Keys

Idempotency Keys

Idempotentlik kalitlari

Bankomatdan pul yechayotganda, tarmoq bir lahzaga uzilib, ekranda "kutilmoqda" chiqib qoladi. Siz яна "tasdiqlash" tugmasini bosasiz. Yaxshi ishlab chiqilgan bankomat buni BILADI: u bitta AMALNI ikki marta bajarmaydi, chunki u har bir so'rovni NOYOB "amal raqami" bilan belgilaydi va shu raqamni ALLAQACHON bajarilgan bo'lsa, natijani QAYTA HISOBLAMASDAN eski natijani qaytaradi. Idempotentlik kaliti (Idempotency Key) — API'da aynan shu himoyani beradi: mijoz HAR BIR mantiqiy amal uchun noyob kalit yuboradi, server esa bitta kalitni IKKI MARTA bajarmaydi.

example.go
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strconv"
	"strings"
	"sync"
)

type Book struct {
	ID     string  `json:"id"`
	Title  string  `json:"title"`
	Author string  `json:"author"`
	Price  float64 `json:"price"`
}

type BookStore struct {
	mu     sync.Mutex
	books  map[string]*Book
	nextID int
}

func NewBookStore() *BookStore { return &BookStore{books: make(map[string]*Book)} }

func (s *BookStore) Create(title, author string, price float64) *Book {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.nextID++
	id := strconv.Itoa(s.nextID)
	book := &Book{ID: id, Title: title, Author: author, Price: price}
	s.books[id] = book
	return book
}

type IdempotencyStore struct {
	mu      sync.Mutex
	results map[string]*Book
}

// GetOrCreate — key ALLAQACHON ko'rilgan bo'lsa, ESKI natijani qaytaradi (replayed=true);
// aks holda create() ni chaqirib, natijani key bilan ESLAB QOLADI
func (s *IdempotencyStore) GetOrCreate(key string, create func() *Book) (book *Book, replayed bool) {
	s.mu.Lock()
	defer s.mu.Unlock()
	if existing, ok := s.results[key]; ok {
		return existing, true
	}
	book = create()
	s.results[key] = book
	return book, false
}

func main() {
	store := NewBookStore()
	idem := &IdempotencyStore{results: make(map[string]*Book)}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /books", func(w http.ResponseWriter, r *http.Request) {
		key := r.Header.Get("Idempotency-Key")
		var req struct {
			Title  string  `json:"title"`
			Author string  `json:"author"`
			Price  float64 `json:"price"`
		}
		json.NewDecoder(r.Body).Decode(&req)

		book, replayed := idem.GetOrCreate(key, func() *Book {
			return store.Create(req.Title, req.Author, req.Price)
		})
		if replayed {
			w.Header().Set("X-Idempotent-Replayed", "true")
		}
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusCreated)
		json.NewEncoder(w).Encode(book)
	})

	body := `{"title":"Go bilan tanishuv","author":"A. Karimov","price":45000}`

	req1 := httptest.NewRequest("POST", "/books", strings.NewReader(body))
	req1.Header.Set("Idempotency-Key", "key-123")
	rec1 := httptest.NewRecorder()
	mux.ServeHTTP(rec1, req1)
	fmt.Println("1-urinish, replayed:", rec1.Header().Get("X-Idempotent-Replayed"))

	req2 := httptest.NewRequest("POST", "/books", strings.NewReader(body))
	req2.Header.Set("Idempotency-Key", "key-123")
	rec2 := httptest.NewRecorder()
	mux.ServeHTTP(rec2, req2)
	fmt.Println("2-urinish (tarmoq qayta yuborgan), replayed:", rec2.Header().Get("X-Idempotent-Replayed"))

	fmt.Println("bazadagi kitoblar soni:", len(store.books))
}

IdempotencyStore.GetOrCreate — MARKAZIY g'oyani ifodalaydi: key (mijoz Idempotency-Key sarlavhasida yuborgan noyob satr) ALLAQACHON results xaritasida bo'lsa, create() funksiyasi UMUMAN chaqirilmaydi — ESKI natija QAYTARILADI. Aks holda, create() chaqiriladi VA natija key bilan SAQLANADI, keyingi safar QAYTA ishlatish uchun.

E'tibor bering: ikkinchi so'rov (xuddi shu "key-123" bilan) store.Create ni UMUMAN chaqirmadi — natijada store.booksda FAQAT bitta kitob bor, garchi POST /books IKKI marta chaqirilgan bo'lsa ham. Bu — HAQIQIY hayotda mijoz "internet uzildi, yana bosayapman" deganda YUZ beradigan vaziyatni AYNAN takrorlaydi: server bitta AMALNI IKKI MARTA bajarmaydi.

>_ Exercise

Idempotentlikning "faqat BIR KALIT uchun" ishlashini isbotlang.

  • yordamchi send(key string) funksiyasini yozing: berilgan Idempotency-Key bilan bitta POST /books so'rovi yuborsin
  • send("key-123") ni IKKI marta chaqiring (xuddi shu kalit — tarmoq qayta yuborgani)
  • send("key-456") ni BIR marta chaqiring (BOSHQA kalit — bu YANGI, mustaqil amal)
  • oxirida store.books xaritasidagi kitoblar SONINI chop eting

Stuck? Reveal a hint to help you.

Hints (0/3)

Key Takeaway

Key Takeaway:

Idempotentlik kaliti — mijoz tarmoq xatosi tufayli BIR XIL so'rovni QAYTA yuborganda, serverning uni IKKI MARTA bajarib yubormasligini kafolatlaydi; kalit ESLAB QOLINGAN natijani qaytarish orqali, amal FAQAT bir marta haqiqatan sodir bo'ladi.

NEXT UP

API Versioning Strategies

OUTPUT

$ go run main.go
Kodingizni ishga tushiring

Idempotency Keys

Idempotentlik kalitlari

Bankomatdan pul yechayotganda, tarmoq bir lahzaga uzilib, ekranda "kutilmoqda" chiqib qoladi. Siz яна "tasdiqlash" tugmasini bosasiz. Yaxshi ishlab chiqilgan bankomat buni BILADI: u bitta AMALNI ikki marta bajarmaydi, chunki u har bir so'rovni NOYOB "amal raqami" bilan belgilaydi va shu raqamni ALLAQACHON bajarilgan bo'lsa, natijani QAYTA HISOBLAMASDAN eski natijani qaytaradi. Idempotentlik kaliti (Idempotency Key) — API'da aynan shu himoyani beradi: mijoz HAR BIR mantiqiy amal uchun noyob kalit yuboradi, server esa bitta kalitni IKKI MARTA bajarmaydi.

example.go
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strconv"
	"strings"
	"sync"
)

type Book struct {
	ID     string  `json:"id"`
	Title  string  `json:"title"`
	Author string  `json:"author"`
	Price  float64 `json:"price"`
}

type BookStore struct {
	mu     sync.Mutex
	books  map[string]*Book
	nextID int
}

func NewBookStore() *BookStore { return &BookStore{books: make(map[string]*Book)} }

func (s *BookStore) Create(title, author string, price float64) *Book {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.nextID++
	id := strconv.Itoa(s.nextID)
	book := &Book{ID: id, Title: title, Author: author, Price: price}
	s.books[id] = book
	return book
}

type IdempotencyStore struct {
	mu      sync.Mutex
	results map[string]*Book
}

// GetOrCreate — key ALLAQACHON ko'rilgan bo'lsa, ESKI natijani qaytaradi (replayed=true);
// aks holda create() ni chaqirib, natijani key bilan ESLAB QOLADI
func (s *IdempotencyStore) GetOrCreate(key string, create func() *Book) (book *Book, replayed bool) {
	s.mu.Lock()
	defer s.mu.Unlock()
	if existing, ok := s.results[key]; ok {
		return existing, true
	}
	book = create()
	s.results[key] = book
	return book, false
}

func main() {
	store := NewBookStore()
	idem := &IdempotencyStore{results: make(map[string]*Book)}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /books", func(w http.ResponseWriter, r *http.Request) {
		key := r.Header.Get("Idempotency-Key")
		var req struct {
			Title  string  `json:"title"`
			Author string  `json:"author"`
			Price  float64 `json:"price"`
		}
		json.NewDecoder(r.Body).Decode(&req)

		book, replayed := idem.GetOrCreate(key, func() *Book {
			return store.Create(req.Title, req.Author, req.Price)
		})
		if replayed {
			w.Header().Set("X-Idempotent-Replayed", "true")
		}
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusCreated)
		json.NewEncoder(w).Encode(book)
	})

	body := `{"title":"Go bilan tanishuv","author":"A. Karimov","price":45000}`

	req1 := httptest.NewRequest("POST", "/books", strings.NewReader(body))
	req1.Header.Set("Idempotency-Key", "key-123")
	rec1 := httptest.NewRecorder()
	mux.ServeHTTP(rec1, req1)
	fmt.Println("1-urinish, replayed:", rec1.Header().Get("X-Idempotent-Replayed"))

	req2 := httptest.NewRequest("POST", "/books", strings.NewReader(body))
	req2.Header.Set("Idempotency-Key", "key-123")
	rec2 := httptest.NewRecorder()
	mux.ServeHTTP(rec2, req2)
	fmt.Println("2-urinish (tarmoq qayta yuborgan), replayed:", rec2.Header().Get("X-Idempotent-Replayed"))

	fmt.Println("bazadagi kitoblar soni:", len(store.books))
}

IdempotencyStore.GetOrCreate — MARKAZIY g'oyani ifodalaydi: key (mijoz Idempotency-Key sarlavhasida yuborgan noyob satr) ALLAQACHON results xaritasida bo'lsa, create() funksiyasi UMUMAN chaqirilmaydi — ESKI natija QAYTARILADI. Aks holda, create() chaqiriladi VA natija key bilan SAQLANADI, keyingi safar QAYTA ishlatish uchun.

E'tibor bering: ikkinchi so'rov (xuddi shu "key-123" bilan) store.Create ni UMUMAN chaqirmadi — natijada store.booksda FAQAT bitta kitob bor, garchi POST /books IKKI marta chaqirilgan bo'lsa ham. Bu — HAQIQIY hayotda mijoz "internet uzildi, yana bosayapman" deganda YUZ beradigan vaziyatni AYNAN takrorlaydi: server bitta AMALNI IKKI MARTA bajarmaydi.

>_ Exercise

Idempotentlikning "faqat BIR KALIT uchun" ishlashini isbotlang.

  • yordamchi send(key string) funksiyasini yozing: berilgan Idempotency-Key bilan bitta POST /books so'rovi yuborsin
  • send("key-123") ni IKKI marta chaqiring (xuddi shu kalit — tarmoq qayta yuborgani)
  • send("key-456") ni BIR marta chaqiring (BOSHQA kalit — bu YANGI, mustaqil amal)
  • oxirida store.books xaritasidagi kitoblar SONINI chop eting

Stuck? Reveal a hint to help you.

Hints (0/3)

Key Takeaway

Key Takeaway:

Idempotentlik kaliti — mijoz tarmoq xatosi tufayli BIR XIL so'rovni QAYTA yuborganda, serverning uni IKKI MARTA bajarib yubormasligini kafolatlaydi; kalit ESLAB QOLINGAN natijani qaytarish orqali, amal FAQAT bir marta haqiqatan sodir bo'ladi.

NEXT UP

API Versioning Strategies