Rumah > pembangunan bahagian belakang > Golang > Memikirkan semula REST API kami: Membina API Emas

Memikirkan semula REST API kami: Membina API Emas

Susan Sarandon
Lepaskan: 2024-12-06 22:05:23
asal
214 orang telah melayarinya

Rethinking our REST API: Building the Golden API

Pada satu ketika, setiap syarikat mencapai persimpangan di mana mereka perlu berhenti dan menilai semula alatan yang mereka gunakan. Bagi kami, detik itu tiba apabila kami menyedari bahawa API yang menjanakan papan pemuka web kami telah menjadi tidak terurus, sukar untuk diuji dan tidak memenuhi piawaian yang kami tetapkan untuk pangkalan kod kami.

Arcjet terutamanya keselamatan sebagai SDK kod untuk membantu pembangun melaksanakan fungsi keselamatan seperti pengesanan bot, pengesahan e-mel dan pengesanan PII. Ini berkomunikasi dengan API gRPC keputusan berprestasi tinggi dan kependaman rendah kami.

Papan pemuka web kami menggunakan API REST yang berasingan terutamanya untuk mengurus sambungan tapak dan menyemak analitis permintaan yang diproses, namun ini juga termasuk mendaftar pengguna baharu dan mengurus akaun mereka, yang bermaksud ia masih merupakan bahagian penting dalam produk.

Rethinking our REST API: Building the Golden API
Tangkapan skrin halaman analisis permintaan papan pemuka Arcjet.

Jadi, kami memutuskan untuk menyahut cabaran membina semula API kami dari bawah—kali ini dengan fokus pada kebolehselenggaraan, prestasi dan kebolehskalaan. Walau bagaimanapun, kami tidak mahu memulakan projek penulisan semula yang besar - yang tidak pernah berjaya - sebaliknya, kami memutuskan untuk membina asas baharu dan kemudian bermula dengan satu titik akhir API.

Dalam siaran ini, saya akan membincangkan cara kami menghampiri perkara ini.

Sebelum ini pada Arcjet

Apabila kelajuan menjadi keutamaan kami, Next.js menyediakan penyelesaian paling mudah untuk membina titik akhir API yang boleh digunakan bahagian hadapan kami. Ia memberi kami pembangunan tindanan penuh yang lancar dalam satu pangkalan kod, dan kami tidak perlu terlalu risau tentang infrastruktur kerana kami menggunakan Vercel.

Tumpuan kami adalah pada keselamatan kami sebagai SDK kod dan API keputusan kependaman rendah jadi untuk papan pemuka bahagian hadapan tindanan ini membolehkan kami membuat prototaip ciri dengan cepat dengan sedikit geseran.

Timbunan Kami: Next.js, DrizzleORM, useSWR, NextAuth

Walau bagaimanapun, apabila produk kami berkembang, kami mendapati bahawa menggabungkan semua titik akhir API kami dan kod hujung hadapan dalam projek yang sama membawa kepada keadaan kusut masai.

Menguji API kami menjadi rumit (dan sangat sukar dilakukan dengan Next.js pula), dan kami memerlukan sistem yang boleh mengendalikan kedua-dua dalaman dan luaran penggunaan. Semasa kami menyepadukan dengan lebih banyak platform (seperti Vercel, Fly.io dan Netlify), kami menyedari bahawa kelajuan pembangunan sahaja tidak mencukupi. Kami memerlukan penyelesaian yang lebih mantap.

Sebagai sebahagian daripada projek ini, kami juga ingin menangani kebimbangan keselamatan yang berlarutan tentang cara Vercel memerlukan anda mendedahkan pangkalan data anda secara terbuka. Melainkan anda membayar untuk "pengiraan selamat" perusahaan mereka, menyambung ke pangkalan data jauh memerlukannya mempunyai titik akhir awam. Kami lebih suka mengunci pangkalan data kami supaya ia hanya boleh diakses melalui rangkaian peribadi. Pertahanan secara mendalam adalah penting dan ini akan menjadi satu lagi lapisan perlindungan.

Ini menyebabkan kami memutuskan untuk menyahganding UI bahagian hadapan daripada API bahagian belakang.

Memperkenalkan "The Golden API"

Apakah itu "API Emas"? Ia bukan teknologi atau rangka kerja khusus—ia lebih kepada set prinsip ideal yang mentakrifkan API yang dibina dengan baik. Walaupun pembangun mungkin mempunyai pilihan mereka sendiri untuk bahasa dan rangka kerja, terdapat konsep tertentu yang sah merentas susunan teknologi yang kebanyakannya boleh bersetuju untuk membina API berkualiti tinggi.

1. Prestasi dan Skalabiliti

Kami sudah mempunyai pengalaman dalam menyampaikan API berprestasi tinggi . API Keputusan kami digunakan berdekatan dengan pelanggan kami, menggunakan Kubernetes untuk membuat skala secara dinamik dan dioptimumkan untuk respons kependaman rendah .

Kami menganggap persekitaran tanpa pelayan dan penyedia lain, tetapi dengan kluster k8s kami yang sedia ada telah beroperasi, adalah wajar untuk menggunakan semula infrastruktur yang sedia ada: penempatan melalui Octopus Deploy, pemantauan melalui Grafana Jaeger, Loki, Prometheus, dll.

Selepas Rust vs Go dalaman yang singkat dibakar, kami memilih Go untuk kesederhanaan, kelajuan dan sejauh mana ia mencapai matlamat asalnya sokongan yang sangat baik untuk membina perkhidmatan rangkaian berskala . Kami juga sudah menggunakannya untuk API Keputusan dan memahami cara mengendalikan API Go, yang memuktamadkan keputusan untuk kami.

2. Dokumentasi Komprehensif dan Jelas

Menukar API bahagian belakang kepada Go adalah mudah kerana kesederhanaan dan ketersediaan alatan yang hebat. Tetapi terdapat satu tangkapan: kami mengekalkan bahagian hadapan Next.js dan tidak mahu menulis jenis TypeScript secara manual atau mengekalkan dokumentasi berasingan untuk API baharu kami.

Masukkan OpenAPI—sempurna untuk keperluan kita. OpenAPI membolehkan kami mentakrifkan kontrak antara bahagian hadapan dan bahagian belakang, sambil turut berfungsi sebagai dokumentasi kami. Ini menyelesaikan masalah mengekalkan skema untuk kedua-dua belah apl.

3. Keselamatan dan Pengesahan

Menyepadukan pengesahan dalam Go tidaklah terlalu sukar, terima kasih kepada NextAuth yang agak mudah untuk ditiru pada bahagian belakang. NextAuth (kini Auth.js) mempunyai API yang tersedia untuk mengesahkan sesi.

Ini bermakna kami boleh mempunyai klien TypeScript di bahagian hadapan yang dijana daripada spesifikasi OpenAPI kami yang membuat panggilan pengambilan ke API bahagian belakang. Bukti kelayakan disertakan secara automatik dalam panggilan ambil dan bahagian belakang boleh mengesahkan sesi dengan NextAuth.

4. Kebolehujian

Menulis sebarang jenis ujian dalam Go adalah sangat mudah dan terdapat banyak contoh di luar sana, merangkumi topik menguji pengendali HTTP.

Adalah lebih mudah untuk menulis ujian untuk titik akhir Go API baharu berbanding API Next.js, terutamanya kerana kami ingin menguji panggilan keadaan disahkan dan pangkalan data sebenar. Kami dapat dengan mudah menulis ujian untuk penghala Gin dan memutarkan ujian penyepaduan sebenar terhadap pangkalan data Postgres kami menggunakan Testcontainers.

Menyatukan Semuanya

Menulis Spec OpenAPI

Kami bermula dengan menulis spesifikasi OpenAPI 3.0 untuk API kami. Pendekatan OpenAPI-first menggalakkan reka bentuk kontrak API sebelum pelaksanaan, memastikan semua pihak berkepentingan (pembangun, pengurus produk dan pelanggan) bersetuju dengan gelagat dan struktur API sebelum sebarang kod ditulis. Ia menggalakkan perancangan yang teliti dan menghasilkan reka bentuk API yang difikirkan dengan baik yang konsisten dan mematuhi amalan terbaik yang telah ditetapkan. Inilah sebab mengapa kami memilih untuk menulis spesifikasi dahulu dan menjana kod daripadanya, berbanding sebaliknya.

Alat pilihan saya untuk ini ialahAPI Fiddle, yang membantu anda mendraf dan menguji spesifikasi OpenAPI dengan cepat. Walau bagaimanapun, API Fiddle hanya menyokong OpenAPI 3.1 (yang kami tidak boleh gunakan kerana banyak perpustakaan belum menerima pakainya), jadi kami kekal dengan versi 3.0 dan menulis spesifikasi dengan tangan.

Berikut ialah contoh rupa spesifikasi untuk API kami:

openapi: 3.0.0
info:
  title: Arcjet Sites API
  description: A CRUD API to manage sites.
  version: 1.0.0

servers:
  - url: <https://api.arcjet.com/v1>
    description: Base URL for all API operations

paths:
  /teams/{teamId}/sites:
    get:
      operationId: GetTeamSites
      summary: Get a list of sites for a team
      description: Returns a list of all Sites associated with a given Team.
      parameters:
        - name: teamId
          in: path
          required: true
          description: The ID of the team
          schema:
            type: string
      responses:
        "200":
          description: A list of sites
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Site"
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
components:
  schemas:
    Site:
      type: object
      properties:
        id:
          type: string
          description: The ID of the site
        name:
          type: string
          description: The name of the site
        teamId:
          type: string
          description: The ID of the team this site belongs to
        createdAt:
          type: string
          format: date-time
          description: The timestamp when the site was created
        updatedAt:
          type: string
          format: date-time
          description: The timestamp when the site was last updated
        deletedAt:
          type: string
          format: date-time
          nullable: true
          description: The timestamp when the site was deleted (if applicable)
    Error:
      required:
        - code
        - message
        - details
      properties:
        code:
          type: integer
          format: int32
          description: Error code
        message:
          type: string
          description: Error message
        details:
          type: string
          description: Details that can help resolve the issue
Salin selepas log masuk
Salin selepas log masuk

Dengan menggunakan spesifikasi OpenAPI, kami menggunakan OAPI-codegen, alat yang menjana kod Go secara automatik daripada spesifikasi OpenAPI. Ia menjana semua jenis, pengendali dan struktur pengendalian ralat yang diperlukan, menjadikan proses pembangunan lebih lancar.

//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml ../../api.yaml
Salin selepas log masuk
Salin selepas log masuk

Output ialah satu set fail Go, satu mengandungi rangka pelayan dan satu lagi dengan pelaksanaan pengendali. Berikut ialah contoh jenis Go yang dijana untuk objek Site:

// Site defines model for Site.
type Site struct {
    // CreatedAt The timestamp when the site was created
    CreatedAt *time.Time `json:"createdAt,omitempty"`

    // DeletedAt The timestamp when the site was deleted (if applicable)
    DeletedAt *time.Time `json:"deletedAt"`

    // Id The ID of the site
    Id *string `json:"id,omitempty"`

    // Name The name of the site
    Name *string `json:"name,omitempty"`

    // TeamId The ID of the team this site belongs to
    TeamId *string `json:"teamId,omitempty"`

    // UpdatedAt The timestamp when the site was last updated
    UpdatedAt *time.Time `json:"updatedAt,omitempty"`
}
Salin selepas log masuk
Salin selepas log masuk

Dengan adanya kod yang dijana, kami dapat melaksanakan logik pengendali API, seperti ini:

func (s Server) GetTeamSites(w http.ResponseWriter, r *http.Request, teamId string) {
    ctx := r.Context()

    // Check user has permission to access team resources
    isAllowed, err := s.userIsAllowed(ctx, teamId)
    if err != nil {
        slog.ErrorContext(
            ctx,
            "failed to check permissions",
            slogext.Err("err", err),
            slog.String("teamId", teamId),
        )
        SendInternalServerError(ctx, w)
        return
    }

    if !isAllowed {
        SendForbidden(ctx, w)
        return
    }

    // Retrieve sites from database
    sites, err := s.repo.GetSitesForTeam(ctx, teamId)
    if err != nil {
        slog.ErrorContext(
            ctx,
            "list sites for team query returned an error",
            slogext.Err("err", err),
            slog.String("teamId", teamId),
        )
        SendInternalServerError(ctx, w)
        return
    }

    SendOk(ctx, w, sites)
}
Salin selepas log masuk

Pangkalan Data: Menjauhi DrizzleORM

Drizzle ialah ORM yang hebat untuk projek JS dan kami akan menggunakannya semula, tetapi mengalihkan kod pangkalan data daripada Next.js bermakna kami memerlukan sesuatu yang serupa untuk Go.

Kami memilih GORM sebagai ORM kami dan menggunakan Corak Repositori untuk abstrak interaksi pangkalan data. Ini membolehkan kami menulis pertanyaan pangkalan data yang bersih dan boleh diuji.

type ApiRepo interface {
    GetSitesForTeam(ctx context.Context, teamId string) ([]Site, error)
}

type apiRepo struct {
    db *gorm.DB
}

func (r apiRepo) GetSitesForTeam(ctx context.Context, teamId string) ([]Site, error) {
    var sites []Site
    result := r.db.WithContext(ctx).Where("team_id = ?", teamId).Find(&sites)
    if result.Error != nil {
        return nil, ErrorNotFound
    }
    return sites, nil
}
Salin selepas log masuk

Menguji. Semuanya

Ujian adalah kritikal bagi kami. Kami ingin memastikan bahawa semua panggilan pangkalan data telah diuji dengan betul, jadi kami menggunakan Testcontainers untuk memutar pangkalan data sebenar untuk ujian kami, mencerminkan dengan teliti persediaan pengeluaran kami.

openapi: 3.0.0
info:
  title: Arcjet Sites API
  description: A CRUD API to manage sites.
  version: 1.0.0

servers:
  - url: <https://api.arcjet.com/v1>
    description: Base URL for all API operations

paths:
  /teams/{teamId}/sites:
    get:
      operationId: GetTeamSites
      summary: Get a list of sites for a team
      description: Returns a list of all Sites associated with a given Team.
      parameters:
        - name: teamId
          in: path
          required: true
          description: The ID of the team
          schema:
            type: string
      responses:
        "200":
          description: A list of sites
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Site"
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
components:
  schemas:
    Site:
      type: object
      properties:
        id:
          type: string
          description: The ID of the site
        name:
          type: string
          description: The name of the site
        teamId:
          type: string
          description: The ID of the team this site belongs to
        createdAt:
          type: string
          format: date-time
          description: The timestamp when the site was created
        updatedAt:
          type: string
          format: date-time
          description: The timestamp when the site was last updated
        deletedAt:
          type: string
          format: date-time
          nullable: true
          description: The timestamp when the site was deleted (if applicable)
    Error:
      required:
        - code
        - message
        - details
      properties:
        code:
          type: integer
          format: int32
          description: Error code
        message:
          type: string
          description: Error message
        details:
          type: string
          description: Details that can help resolve the issue
Salin selepas log masuk
Salin selepas log masuk

Selepas menyediakan persekitaran ujian, kami menguji semua operasi CRUD seperti yang kami lakukan dalam pengeluaran, memastikan kod kami berfungsi dengan betul.

//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml ../../api.yaml
Salin selepas log masuk
Salin selepas log masuk

Untuk menguji pengendali API kami, kami menggunakan pakej httptest Go dan mengejek interaksi pangkalan data menggunakan Mockery. Ini membolehkan kami menumpukan pada menguji logik API tanpa perlu risau tentang isu pangkalan data.

// Site defines model for Site.
type Site struct {
    // CreatedAt The timestamp when the site was created
    CreatedAt *time.Time `json:"createdAt,omitempty"`

    // DeletedAt The timestamp when the site was deleted (if applicable)
    DeletedAt *time.Time `json:"deletedAt"`

    // Id The ID of the site
    Id *string `json:"id,omitempty"`

    // Name The name of the site
    Name *string `json:"name,omitempty"`

    // TeamId The ID of the team this site belongs to
    TeamId *string `json:"teamId,omitempty"`

    // UpdatedAt The timestamp when the site was last updated
    UpdatedAt *time.Time `json:"updatedAt,omitempty"`
}
Salin selepas log masuk
Salin selepas log masuk

Penggunaan Frontend: OpenAPI-Driven Frontend

Setelah API kami diuji dan digunakan, kami menumpukan perhatian kepada bahagian hadapan.

Panggilan API kami sebelum ini dibuat menggunakan AP ambilan yang disyorkan Next.jsI dengan caching terbina dalam. Untuk paparan yang lebih dinamik, beberapa komponen menggunakan SWR di atas pengambilan jadi kami boleh mendapatkan data yang selamat jenis, muat semula automatik, mengambil panggilan.

Untuk menggunakan API pada bahagian hadapan, kami menggunakan pustaka openapi-typescript, yang menjana jenis TypeScript berdasarkan skema OpenAPI kami. Ini memudahkan untuk menyepadukan bahagian belakang dengan bahagian hadapan kami tanpa perlu menyegerakkan model data secara manual. Ini mempunyai Tanstack Query terbina dalam, yang menggunakan pengambilan di bawah hud, tetapi turut disegerakkan ke skema kami.

Kami memindahkan titik akhir API secara beransur-ansur ke pelayan Go baharu dan membuat peningkatan kecil di sepanjang jalan. Jika anda membuka pemeriksa penyemak imbas anda, anda akan melihat permintaan baharu tersebut pergi ke api.arcjet.com

Rethinking our REST API: Building the Golden API
Tangkapan skrin pemeriksa penyemak imbas menunjukkan panggilan API ke hujung belakang Go baharu.

Senarai Semak Emas

Jadi, adakah kita mencapai Golden API yang sukar difahami? Mari tandai kotak:

  • Prestasi dan Kebolehskalaan – API digunakan pada kluster k8s sedia ada kami yang telah ditala untuk prestasi. Kami mempunyai metrik terperinci dan boleh menskalakannya mengikut keperluan.
  • Dokumentasi Komprehensif dan Jelas – Spesifikasi OpenAPI menyediakan satu sumber kebenaran kerana kod dihasilkan daripadanya dan bukannya sebaliknya. Menggunakan pelanggan yang dijana bermakna pasukan kami mudah untuk bekerja dengan API.
  • Keselamatan dan Pengesahan – Kami sudah menggunakan Go dalam pengeluaran supaya kami boleh menyalin amalan keselamatan kami. Disahkan dikendalikan oleh NextAuth.
  • Kebolehujian – Kami melaksanakan ujian unit pengendali dan ujian integrasi menggunakan Testcontainers, peningkatan ketara pada API Next.js kami.

Kami pergi lebih jauh dengan:

  • Pemantauan – Menggunakan kluster k8s sedia ada bermakna kami mewarisi keupayaan pengesanan, pengelogan dan metrik yang telah disediakan. Menambah instrumentasi OpenTelemetry pada Gin adalah remeh.
  • Kesederhanaan – Go pada asalnya direka untuk API dan ia menunjukkan. Kod kami lebih bersih dan lebih boleh diselenggara.

Akhirnya, kami gembira dengan hasilnya. API kami lebih pantas, lebih selamat dan lebih baik diuji. Peralihan kepada Go adalah berbaloi dan kami kini berada pada kedudukan yang lebih baik untuk meningkatkan dan mengekalkan API kami apabila produk kami berkembang.

Atas ialah kandungan terperinci Memikirkan semula REST API kami: Membina API Emas. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

sumber:dev.to
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Artikel terbaru oleh pengarang
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan