A detailed backend reference

Handler, Service & Repository.

Penelusuran dari prinsip dasar tentang perjalanan satu request HTTP di dalam server Anda: lewat routing, tiga lapisan arsitektur, rantai middleware, dan request context yang mengikat semuanya. Implementasi lengkap di Go dan Python ditampilkan berdampingan.

Separation of Concerns Request Lifecycle Middleware Chain Go + Python / full code 15 sections Versi Sederhana
Part I / The Request's Journey01 / 15

01 Klien & Server

Semuanya bermula dari dua peserta yang bicara lewat HTTP: klien (browser, aplikasi mobile, layanan lain) dan server. Klien mengirim request; server akhirnya mengirim balik respons.

Kita sudah mempelajari siklus hidup yang eksternal terhadap server, bagaimana request dikemas, dikirim melewati jaringan, dan bagaimana respons kembali. Yang belum kita telusuri adalah apa yang terjadi di dalam server: rangkaian panjang peristiwa antara sesaat request tiba dan sesaat respons keluar. Rangkaian internal itulah subjek manual ini.

Client browser / app Server your backend HTTP request → ← HTTP response
The two ends of every interaction. This manual zooms into the right-hand box.
Part I / The Request's Journey02 / 15

02 Siklus Hidup Request di Dalam Server

Dari saat request tiba di server sampai saat respons dikembalikan, banyak yang terjadi. Kita sebut ini request lifecycle. Untuk memahami arsitekturnya, bantu menelusuri request dari atas ke bawah, mengikutinya dalam urutan persis prosesnya: kedatangan, routing, handler, service, repository, dan akhirnya respons dalam perjalanan keluar.

Urutan atas-ke-bawah ini disengaja. Setiap topik dibangun di atas yang terakhir, mengikuti jalur yang secara fisik ditempuh data melewati kode Anda.

The journey of one request Entry Point Routing Handler / Controller Service Repository → Database OS forwards :3000 /users/:id → fn
The five internal stops. The response then climbs back up the same path.
Part I / The Request's Journey03 / 15

03 Titik Masuk & Routing

Saat request tiba, sistem operasi meneruskan request HTTP ke port mana pun tempat server Anda mendengarkan, :3000, :4000, atau port apa pun yang Anda konfigurasi. Titik tempat server Anda pertama menerima request itu adalah entry point-nya. Server Anda selalu mendengarkan di port itu, dan mendengarkan itulah cara ia menangkap request yang masuk.

Routing

Tepat setelah entry point datang routing. Server mengekspos banyak route, /users, /users/123, route dinamis seperti /users/:id, dan seterusnya. Algoritma routing memeriksa method dan path request yang masuk lalu memetakannya ke handler tertentu: fungsi yang sudah didefinisikan untuk route itu.

Definition / Handler

Handler (juga disebut controller) adalah fungsi apa pun yang Anda definisikan untuk menangani request bagi sebuah route. Seluruh kerja routing adalah memutuskan handler mana yang berjalan untuk request masuk yang mana.

Part I / The Request's Journey04 / 15

04 Kenapa Tiga Lapisan?

Begitu routing memilih handler, kita menjumpai struktur tiga bagian di jantung backend yang tertata rapi: handler (controller), service, dan repository. Pertanyaan yang wajar: kenapa memecahnya menjadi tiga komponen alih-alih menjejalkan semua logika ke dalam satu handler?

Jawaban jujurnya: tidak ada keharusan untuk memisahkannya. Anda bisa melakukan semuanya di satu handler. Pemisahan adalah pola desain: pilihan yang membelikan Anda manfaat nyata:

  • Codebase Anda jadi skalabel saat tumbuh.
  • Lebih mudah dirawat seiring waktu.
  • Lebih mudah menambah fitur tanpa merusak bagian yang tak berhubungan.
  • Lebih mudah di-debug karena tiap lapisan punya satu tanggung jawab yang jelas.

Inilah prinsip separation of concerns. Sisa Bagian II menapaki tiap lapisan dalam urutan request menjumpainya.

Handler controller data in / out validate status codes Service business logic processing orchestrate no HTTP Repository database build query read / write one job each
One responsibility per layer. The request flows left→right, the result returns right→left.
Part II / The Three Layers05 / 15

05 Lapisan Controller / Handler

Handler adalah titik masuk untuk sebuah route setelah routing mencocokkannya. Tugas besarnya adalah mengendalikan alur data: dari klien ke dalam server, dan dari server kembali ke klien. Yang penting, di hampir semua framework dan bahasa, handler Anda diberi dua hal oleh runtime itu sendiri.

Provided by the runtimeThe request object

Membawa semua yang dikirim klien: method, path, header, parameter query, body. Anda tidak membuatnya; Go, Express, Flask, dll. menyerahkannya ke Anda di tiap request.

Provided by the runtimeThe response object

Pegangan tempat Anda men-set status code, memodifikasi header respons, dan mengirim body kembali. Juga disediakan otomatis; Anda menerimanya, tidak menyusunnya.

Dengan dua objek itu di tangan, handler menjalankan alur kerja yang presisi dan berurutan.

  1. Ekstraksi Data. Ambil data yang relevan dari objek request. Apa yang diambil tergantung method: GET → parameter query; POST / PUT / PATCH → body request; DELETE → biasanya tidak ada, kadang body.
  2. Binding (Deserialisasi). Body tiba sebagai string JSON karena JSON format yang bisa diserialkan dan bepergian dengan baik melewati batas front-end/back-end. Handler men-deserialize-nya ke tipe natif bahasa, struct Go, dict/kelas Python, struct Rust. Framework menyebut ini binding. Jika gagal, berhenti seketika dan kembalikan 400 Bad Request.
  3. Validasi. Konfirmasi data cocok dengan bentuk yang diharapkan: field wajib ada, tipe benar, tidak ada payload jahat. Validasi semuanya dari klien eksternal: path param, query param, dan body, dan se-spesifik mungkin.
  4. Transformasi. Opsional, bentuk ulang data yang sudah divalidasi demi kenyamanan lapisan di bawahnya, misalnya menyuntikkan nilai default.
  5. Delegasi. Serahkan data yang bersih, tervalidasi, dan tertransformasi (plus konteks seperti ID pengguna yang terautentikasi) turun ke lapisan Service.
  6. Mengirim Respons. Saat service mengembalikan, pilih status code yang benar dan kirim respons akhir kembali ke klien.

Catatan soal Node.js vs Go & Python

Di aplikasi Node.js/Express, langkah deserialisasi sering terjadi di hulu dalam middleware (body parser json()), jadi JSON sudah berupa objek JavaScript saat handler Anda berjalan. Di Go dan Python Anda biasanya men-deserialize secara eksplisit di dalam handler, ke struct, dictionary, atau kelas.

Langkah 1-2: Ekstrak & bind body; 400 saat gagal

create_book / handler
// CreateBookRequest is our native format, the bind target.
type CreateBookRequest struct {
    Title  string `json:"title"`
    Author string `json:"author"`
}

func CreateBookHandler(w http.ResponseWriter, r *http.Request) {
    var req CreateBookRequest

    // Step 1 + 2: extract the body and deserialize (bind) into the struct.
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        // Deserialization failed -> the payload is malformed.
        http.Error(w, "invalid request body", http.StatusBadRequest) // 400
        return // terminate the request here; do not proceed.
    }
    // ... validation, transformation, delegation follow ...
}

Langkah 3-4: Validasi, lalu transformasi (suntik default untuk query param opsional)

Aturan desain API yang baik: buat parameter query opsional sedapat mungkin. Anggap GET /books?sort=name|date. Jika klien tidak mengirim apa pun, validasi harus tetap lulus, jadi langkah transformasi menyuntikkan default yang masuk akal.

list_books / validate + transform
func ListBooksHandler(w http.ResponseWriter, r *http.Request) {
    sort := r.URL.Query().Get("sort") // "" if absent

    // VALIDATION: if present, it must be one of the allowed values.
    if sort != "" && sort != "name" && sort != "date" {
        http.Error(w, "sort must be 'name' or 'date'", http.StatusBadRequest)
        return
    }

    // TRANSFORMATION: query params are optional -> inject a default.
    if sort == "" {
        sort = "date" // downstream layers never see an empty value
    }

    books, err := bookService.ListBooks(r.Context(), sort) // delegate
    if err != nil {
        http.Error(w, "could not fetch books", http.StatusInternalServerError) // 500
        return
    }
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(books) // 200 with the array of books
}
Part II / The Three Layers06 / 15

06 Lapisan Service

Lapisan service adalah tempat pemrosesan sebenarnya: logika bisnisnya, terjadi. Satu aturan terpenting mengaturnya:

The Golden Rule of Services

Lapisan service tidak boleh tahu sedikit pun soal HTTP. Tanpa objek request/response, tanpa status code, tanpa urusan validasi. Jika Anda melirik sebuah method service, Anda tidak seharusnya bisa tahu ia dipakai di API. Ia cuma fungsi: data masuk, pemrosesan, data keluar.

Isolasi inilah yang menjaga sistem tetap terpisah (decoupled). Service memutuskan apa yang dilakukan; handler memutuskan bagaimana melaporkannya lewat HTTP. Di situlah garis tanggung jawab kita tarik.

Orkestrasi

Satu method service bisa berbuat banyak. Ia bisa memanggil satu atau beberapa method repository dan menggabungkan hasilnya, membuat panggilan API eksternal, mengirim email, memicu notifikasi, apa pun yang dibutuhkan operasi bisnis. Peran koordinasi ini disebut orkestrasi: service menjahit data dari banyak sumber dan mengembalikan satu hasil bersih ke handler. Service yang hanya mengirim email mungkin tidak pernah menyentuh repository sama sekali.

book_service / pure business logic
// Notice: no http.Request, no ResponseWriter, no status codes.
// You cannot tell from this signature that it serves an API.
func (s *BookService) ListBooks(ctx context.Context, sort string) ([]Book, error) {
    // Orchestration: call the repository for the data it needs.
    books, err := s.repo.FindAllBooks(ctx, sort)
    if err != nil {
        return nil, err // bubble the error up; the handler decides the code
    }
    // Could also: enrich, merge other repo calls, send notifications...
    return books, nil
}

// A service that needs no database at all is perfectly valid:
func (s *BookService) NotifyOwner(email string) error {
    return s.mailer.Send(email, "Your book was added")
}
Part II / The Three Layers07 / 15

07 Lapisan Repository

Repository (atau lapisan database) punya satu perhatian: bicara ke database. Ia menerima data dari service, menyusun query database: untuk insert, filter, atau sortir, menjalankannya, dan mengembalikan hasil mentah ke atas kepada service.

Single Responsibility

Method repository harus melakukan satu hal dan mengembalikan satu jenis data. Jangan menulis satu method yang, tergantung parameter opsional, mengembalikan satu buku atau semua buku. Tulis dua method terpisah: FindAllBooks() dan FindBookByID(id).

book_repository / one job per method
// ONE method, ONE shape of result: all books, sorted.
func (r *BookRepo) FindAllBooks(ctx context.Context, sort string) ([]Book, error) {
    // Build the query from the data the service handed down.
    query := fmt.Sprintf("SELECT id, title, author FROM books ORDER BY %s", sort)
    rows, err := r.db.QueryContext(ctx, query)
    if err != nil { return nil, err }
    defer rows.Close()

    var books []Book
    for rows.Next() {
        var b Book
        rows.Scan(&b.ID, &b.Title, &b.Author)
        books = append(books, b)
    }
    return books, nil
}

// A SEPARATE method for the single-book case. No optional toggles.
func (r *BookRepo) FindBookByID(ctx context.Context, id int) (Book, error) {
    var b Book
    err := r.db.QueryRowContext(ctx,
        "SELECT id, title, author FROM books WHERE id = $1", id).
        Scan(&b.ID, &b.Title, &b.Author)
    return b, err
}
Part II / The Three Layers08 / 15

08 Siklus Hidup Lengkap, End to End

Merangkai lapisan-lapisannya, inilah round trip lengkap untuk satu panggilan API, mis. GET /books:

  1. Binding. Handler mengekstrak data dari objek request dan men-deserialize-nya ke format natif.
  2. Validasi & Transformasi. Handler memvalidasi data dan menyuntikkan default / membentuk ulang sesuai kebutuhan.
  3. Panggilan service. Handler mendelegasikan ke service, yang melakukan pemrosesan sungguhan, memanggil repository untuk kerja database apa pun.
  4. Hasil kembali. Repository mengembalikan baris ke service; service mengembalikan hasil bersih ke handler.
  5. Response
    The handler picks the status code and sends the response. 200 / 201 / 204 on success; 400 for client error; 500 for server error.
200 OK 201 Created 204 No Content 400 Bad Request 500 Internal Server Error
Client Handlervalidate Servicelogic Repositoryquery DB request flows → down the layers ← result returns back up → response
Handlers move data formats in and out / the service does the processing / the repository does the database work.
Part III / Middleware09 / 15

09 Apa Itu Middleware

Sekarang putar ulang siklus hidupnya, tapi perhatikan celahnya. Antara entry point dan routing, antara routing dan handler, dan antara handler dan respons, ada batas-batas: titik tempat fungsi ekstra bisa berjalan. Fungsi yang berjalan di celah-celah itu adalah middleware. Namanya harfiah: mereka dieksekusi di tengah konteks eksekusi lainnya.

Middleware opsional. Bisa banyak, bisa tidak ada sama sekali; request bisa mengalir lurus dari routing ke handler. Anda menambahkannya hanya saat ada kebutuhan yang memintanya.

Middleware pada dasarnya jenis handler khusus. Seperti handler biasa ia menerima objek request dan objek response dari runtime, jadi ia juga bisa membaca nilai dari request, memodifikasi header respons, bahkan mengirim respons langsung ke klien. Tapi ia menerima hal ketiga juga, yang dijelaskan Bagian 10.

entry MW 1 routing MW 2 handler MW 3 response each red box is an optional middleware sitting in a boundary
Middlewares occupy the boundaries between execution contexts.
Part III / Middleware10 / 15

10 Fungsi next()

Hal ketiga yang diterima middleware adalah next, sebuah fungsi. Memanggil next() menyerahkan eksekusi ke konteks berikutnya: entah middleware berikutnya, langkah routing, atau handler akhir. Begitulah request menyeberang dari satu batas ke batas berikutnya.

Two ways a middleware ends

(1) Lakukan kerjanya, lalu panggil next() untuk melanjutkan rantai. (2) Tidak memanggil next() dan sebagai gantinya langsung mengirim respons: short-circuit request sebelum ia pernah mencapai handler. Keluar lebih awal inilah persis kenapa cek auth yang gagal atau rate limit yang tersandung bisa menghentikan request dengan murah.

Karena middleware punya objek request dan response, ia bisa membaca dan memodifikasi request, dan bisa menghentikan request tepat di tempatnya dengan mengembalikan respons. Itulah seluruh batas kekuatan middleware.

middleware shape / req, res, next
// In Go, middleware wraps the "next" handler. Calling next.ServeHTTP
// is the equivalent of next(): pass execution along the chain.
func LoggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Printf("%s %s", r.Method, r.URL.Path) // do work

        // EARLY EXIT example (short-circuit, never calls next):
        if r.Header.Get("X-Blocked") == "yes" {
            http.Error(w, "forbidden", http.StatusForbidden)
            return // request stops here
        }

        next.ServeHTTP(w, r) // === next(): continue the chain ===
    })
}

Kenapa pakai middleware sama sekali?

Alasan yang sama kita memakai fungsi: menghindari mengulang baris yang sama di mana-mana. Backend bisa melayani ribuan atau jutaan request dan mengekspos ratusan endpoint. Operasi umum, keamanan, logging, autentikasi, parsing, kompresi, perlu berjalan untuk setiap request. Tanpa middleware Anda harus menduplikasi kode itu di tiap handler. Bahkan mengekstrak fungsi pembantu tetap memaksa Anda memanggilnya di tiap handler. Middleware memusatkan logika umum dan menerapkannya otomatis di sepanjang rantai.

Part III / Middleware11 / 15

11 Kenapa Urutan Penting

Setiap middleware memakai next() untuk menyerahkan eksekusi ke yang sesudahnya, jadi request mengalir melewatinya secara sekuensial, dalam urutan Anda mendaftarkannya. Urutan itu bukan hiasan, ia mengubah perilaku.

Contoh paling tajam adalah penanganan error global, yang biasanya ditaruh paling akhir. Karena request mengalir dalam satu arah, middleware hanya bisa menangkap error yang berasal di hulu darinya. Jika Anda menaruh error handler di tengah, error yang dilempar belakangan di handler akan mengalir melewatinyanya dan tak pernah tertangkap. Letakkan paling akhir dan ia bisa menangkap error dari mana pun di awalnya.

A sensible default ordering

CORS dulu (tolak origin asing sedini mungkin) → logging → autentikasi → pemeriksaan khusus aplikasi → handler → penanganan error global paling akhir. Luangkan waktu sungguhan memutuskan urutan ini untuk aplikasi Anda.

Part III / Middleware12 / 15

12 Middleware yang Umum

Masing-masing berikut memenuhi syarat middleware karena dua alasan yang sama: ia harus berjalan untuk setiap request, dan ia perlu membaca request dan/atau memodifikasi respons.

SecurityCORS

Browser menegakkan same-origin policy: aplikasi web di example.com hanya boleh mengakses resource dari example.com kecuali server jarak jauh mengirim header yang benar. Middleware CORS membaca origin request (disediakan otomatis oleh runtime); jika cocok dengan origin frontend yang diizinkan, ia menambah header respons yang sesuai supaya browser tidak memblokir respons, lalu memanggil next(). Jika tidak, ia mengabaikan header itu dan browser memblokirnya sebagai bawaan. Ditaruh di awal.

SecuritySecurity headers

Men-set header seperti Content-Security-Policy di tiap respons, lalu meneruskan ke middleware berikutnya.

SecurityAuthentication

Mengekstrak token (JWT, session ID, dll.) dari header atau payload dan memverifikasinya. Saat gagal, ia mengembalikan 401 Unauthorized seketika dan menghentikan request, tanpa handler, tanpa middleware lanjutan. Saat sukses, ia mengekstrak detail pengguna (user ID, role, izin) dan menyimpannya di request context sebelum memanggil next().

SecurityRate limiting

Melacak berapa banyak request yang dibuat sebuah IP dalam jendela yang Anda tetapkan (mis. 30 panggilan dalam 2 detik). Lewat ambang, ia mengembalikan 429 Too Many Requests untuk melindungi resource server; di bawahnya, ia memanggil next().

ObservabilityLogging & monitoring

Mencatat detail request, path, method, parameter query, body, ke terminal atau file log untuk debugging, pelaporan, dan audit.

ReliabilityGlobal error handling

Menangkap error tak terstruktur apa pun dari hulu mana pun, memutuskan apakah itu error klien (4xx) atau server (5xx), dan mengembalikan pesan bersih terstruktur (sering dengan pesan dan kode error yang bisa dipetakan frontend ke pesan ramah). Ditaruh paling akhir.

PerformanceCompression

Mengompresi respons besar (mis. gzip) supaya bepergian efisien; browser modern mendekompresinya secara transparan kembali ke format yang bisa dipakai.

ConvenienceData parsing

Serialisasi/deserialisasi, bahkan validasi/transformasi, bisa didelegasikan ke middleware (mis. body parser) supaya handler tidak mengulanginya. Persis inilah kenapa body parser json() Node membuat JSON sudah berupa objek JS saat handler berjalan.

Middleware CORS & Autentikasi dalam kode

cors + auth middleware
var allowedOrigin = "https://app.example.com"

func CORSMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        origin := r.Header.Get("Origin") // runtime gives us this
        if origin == allowedOrigin {
            w.Header().Set("Access-Control-Allow-Origin", origin)
        }
        next.ServeHTTP(w, r) // pass along; browser blocks if header absent
    })
}

func AuthMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        userID, role, err := verifyToken(token)
        if err != nil {
            http.Error(w, "unauthorized", http.StatusUnauthorized) // 401, stop
            return
        }
        // SUCCESS: stash identity in the request context, then continue.
        ctx := context.WithValue(r.Context(), "userID", userID)
        ctx = context.WithValue(ctx, "role", role)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}
Part IV / Request Context13 / 15

13 Apa Itu Request Context

Middleware auth tadi melakukan sesuatu yang halus: ia menyimpan user ID dan role di suatu tempat supaya handler berikutnya bisa membacanya. "Suatu tempat" itu adalah request context.

Definition / Request Context

Sepotong penyimpanan atau state: biasanya key–value store sederhana, yang ter-scope ke satu request HTTP. Setiap request mendapat konteksnya sendiri; ia tidak bocor ke request lain. Setiap framework di setiap bahasa (Node, Go, Python) mengirimkan implementasi konsep ini; mekanikanya berbeda, gagasannya sama.

Kenapa ia ada? Karena request melewati banyak batas fungsi terisolasi: CORS, logging, routing, auth, pemeriksaan izin, handler, error handler. Konteks memberi mereka semua satu tempat bersama untuk membaca dan menulis state tanpa menggandeng erat (tight coupling), tanpa satu middleware harus menyerahkan nilai ke berikutnya secara manual.

auth MW logging perm check handler error MW REQUEST CONTEXT · shared key–value state, scoped to this one request { user_id, role, request_id, deadline }
Every boundary can read and write the same per-request store: no manual hand-off needed.
Part IV / Request Context14 / 15

14 Kasus Pakai / Meneruskan Data Autentikasi

Kasus kanonisnya. Middleware auth memverifikasi kredensial, mengekstrak userid dan role, dan menuliskannya ke konteks. Jauh di hilir, handler POST /books perlu menstempel buku baru dengan pemiliknya. Alih-alih memercayai userid yang dikirim di payload JSON klien, ia membaca ID dari konteks.

Security, why this matters

Jika Anda mengambil userid dari payload klien, klien jahat bisa mengirim ID orang lain_ dan bertindak sebagai mereka. Membaca identitas dari konteks, yang hanya diisi middleware auth terverifikasi Anda, menutup lubang itu. Role yang tersimpan di konteks yang sama menggerakkan pemeriksaan izin (apakah pengguna ini admin? apakah ia punya akses tulis?).

handler reads identity from context
func CreateBookHandler(w http.ResponseWriter, r *http.Request) {
    var req CreateBookRequest
    json.NewDecoder(r.Body).Decode(&req)

    // Read the trusted user ID FROM THE CONTEXT, not from req.
    // The auth middleware put it there after verifying the token.
    userID := r.Context().Value("userID").(int)
    role := r.Context().Value("role").(string)

    if role != "admin" && role != "user" {
        http.Error(w, "forbidden", http.StatusForbidden)
        return
    }

    // Persist with the SERVER-VERIFIED owner id, never the client's.
    book, _ := bookService.Create(r.Context(), req, userID)
    w.WriteHeader(http.StatusCreated) // 201
    json.NewEncoder(w).Encode(book)
}
Part IV / Request Context15 / 15

15 Kasus Pakai / Tracing & Pembatalan

Tracing request

Middleware di awal bisa membuat ID unik (UUID) dan menyimpannya di konteks. Untuk seluruh siklus hidup request itu, setiap baris log bisa menyertakan ID ini, dan panggilan keluar ke microservice lain bisa meneruskannya di header seperti X-Request-ID. Saat Anda kemudian mengaudit log untuk men-debug masalah, ID tunggal itu memungkinkan Anda menelusuri satu request melewati setiap layanan yang disentuhnya: dari mana ia mulai dan ke mana-mana ia pergi.

request-id middleware
func RequestIDMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        id := uuid.NewString() // one unique id for this request
        ctx := context.WithValue(r.Context(), "requestID", id)
        w.Header().Set("X-Request-ID", id) // echo it back / forward it
        log.Printf("[%s] %s %s", id, r.Method, r.URL.Path)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

Pembatalan, sinyal abort & deadline

Request context juga tempat standar untuk membawa sinyal pembatalan dan deadline. Jika klien putus atau deadline lewat, sinyal itu menyebar ke panggilan eksternal di hilir, jadi service tidak pernah menggantung selamanya menunggu kerja yang tidak lagi penting. Di Go ini context.Context bawaan dengan WithTimeout; di Python muncul sebagai timeout / cancellation token yang diteruskan sepanjang rantai panggilan.

The whole picture

Request masuk di entry point, mengalir lewat rantai middleware dan routing yang berurutan, mendarat di handler yang memvalidasi dan mentransformasi data, mendelegasikan ke service yang mengorkestrasi logika bisnis, yang memanggil repository untuk kerja database, semuanya sambil konteks per-request membawa identitas, trace ID, dan deadline di sisinya. Hasilnya naik kembali, dan handler memilih status code lalu mengirim respons. Itulah siklus hidup request lengkap.