Irgendwann kommt jedes Unternehmen an einen Scheideweg, an dem es innehalten und die von ihm verwendeten Tools neu bewerten muss. Für uns kam dieser Moment, als uns klar wurde, dass die API, die unser Web-Dashboard antreibt, nicht mehr zu verwalten und schwer zu testen war und nicht den Standards entsprach, die wir für unsere Codebasis festgelegt hatten.
Arcjet ist in erster Linie ein Security-as-Code-SDK, das Entwicklern bei der Implementierung von Sicherheitsfunktionen wie Bot-Erkennung, E-Mail-Validierung und PII-Erkennung hilft. Dies kommuniziert mit unserer leistungsstarken, Entscheidungs-gRPC-API mit geringer Latenz.
Unser Web-Dashboard verwendet eine separate REST-API in erster Linie zur Verwaltung von Site-Verbindungen und zur Überprüfung der Analyse verarbeiteter Anfragen. Dazu gehört jedoch auch die Anmeldung neuer Benutzer und die Verwaltung ihres Kontos, was bedeutet, dass dies immer noch ein wichtiger Teil des Produkts ist.
Screenshot der Seite zur Anforderungsanalyse des Arcjet-Dashboards.
Deshalb haben wir uns entschieden, die Herausforderung anzunehmen, unsere API von Grund auf neu zu erstellen – dieses Mal mit Schwerpunkt auf Wartbarkeit, Leistung und Skalierbarkeit. Allerdings wollten wir uns nicht auf ein riesiges Umschreibungsprojekt einlassen – das nie gut klappt – stattdessen haben wir beschlossen, eine neue Grundlage zu schaffen und dann mit einem einzigen API-Endpunkt zu beginnen.
In diesem Beitrag werde ich besprechen, wie wir das angegangen sind.
Zuvor auf Arcjet
Als Geschwindigkeit für uns oberste Priorität hatte, bot Next.js die bequemste Lösung zum Erstellen von API-Endpunkten, die unser Frontend nutzen konnte. Es ermöglichte uns eine nahtlose Full-Stack-Entwicklung innerhalb einer einzigen Codebasis und wir mussten uns nicht allzu viele Gedanken über die Infrastruktur machen, da wir auf Vercel bereitgestellt haben.
Unser Fokus lag auf unserer Sicherheit als Code-SDK und Entscheidungs-API mit geringer Latenz, sodass wir mit diesem Stack für das Frontend-Dashboard schnell und ohne Reibungsverluste Prototypen von Funktionen erstellen konnten.
Unser Stack: Next.js, DrizzleORM, useSWR, NextAuth
Als sich unser Produkt weiterentwickelte, stellten wir jedoch fest, dass die Kombination aller unserer API-Endpunkte und Frontend-Codes im selben Projekt zu einem Durcheinander führte.
Das Testen unserer API wurde umständlich (und ist mit Next.js ohnehin sehr schwierig), und wir brauchten ein System, das sowohl intern als auch externVerbrauch. Als wir uns in weitere Plattformen (wie Vercel, Fly.io und Netlify) integriert haben, wurde uns klar, dass die Entwicklungsgeschwindigkeit allein nicht ausreichte. Wir brauchten eine robustere Lösung.
Im Rahmen dieses Projekts wollten wir auch ein anhaltendes Sicherheitsproblem ansprechen, das darin besteht, dass Vercel von Ihnen verlangt, Ihre Datenbank öffentlich zugänglich zu machen. Sofern Sie nicht für die Unternehmens-„sichere Datenverarbeitung“ bezahlen, ist für die Verbindung mit einer Remote-Datenbank ein öffentlicher Endpunkt erforderlich. Wir ziehen es vor, unsere Datenbank zu sperren, sodass der Zugriff nur über ein privates Netzwerk möglich ist. Eine umfassende Verteidigung ist wichtig und dies wäre eine weitere Schutzebene.
Dies führte dazu, dass wir beschlossen, die Frontend-Benutzeroberfläche von der Backend-API zu entkoppeln.
Vorstellung von „The Golden API“
Was ist „The Golden API“? Es handelt sich nicht um eine bestimmte Technologie oder ein bestimmtes Framework, sondern vielmehr um eine Reihe idealer Prinzipien, die eine gut aufgebaute API definieren. Während Entwickler möglicherweise ihre eigenen Vorlieben für Sprachen und Frameworks haben, gibt es bestimmte Konzepte, die für alle Tech-Stacks gültig sind und auf die sich die meisten für die Erstellung einer hochwertigen API einigen können.
1. Leistung und Skalierbarkeit
Wir haben bereits Erfahrung in der Bereitstellung leistungsstarker APIs.Unsere Entscheidungs-API wird in der Nähe unserer Kunden bereitgestellt, nutzt Kubernetes zur dynamischen Skalierung und ist für Antworten mit geringer Latenz optimiert .
Wir haben serverlose Umgebungen und andere Anbieter in Betracht gezogen, aber da unsere bestehenden k8s-Cluster bereits in Betrieb waren, war es am sinnvollsten, die vorhandene Infrastruktur wiederzuverwenden: Bereitstellungen durch Octopus Deploy, Überwachung durch Grafana Jaeger, Loki, Prometheus usw.
Nach einem kurzen internen Rust-vs-Go-Test entschieden wir uns für Go aufgrund seiner Einfachheit, Geschwindigkeit und weil es seine ursprünglichen Ziele einer hervorragenden Unterstützung für den Aufbau skalierbarer Netzwerkdienste erreicht . Wir verwenden es auch bereits für die Entscheidungs-API und verstehen, wie man Go-APIs bedient, was die Entscheidung für uns finalisiert hat.
2. Umfassende und klare Dokumentation
Die Umstellung der Backend-API auf Go war dank ihrer Einfachheit und der Verfügbarkeit großartiger Tools unkompliziert. Aber es gab einen Haken: Wir behielten das Next.js-Frontend bei und wollten weder TypeScript-Typen manuell schreiben noch eine separate Dokumentation für unsere neue API pflegen.
Jetzt kommt OpenAPI ins Spiel – genau das Richtige für unsere Anforderungen. OpenAPI ermöglicht es uns, einen Vertrag zwischen Frontend und Backend zu definieren und dient gleichzeitig als unsere Dokumentation. Dies löst das Problem der Pflege eines Schemas für beide Seiten der App.
3. Sicherheit und Authentifizierung
Die Integration der Authentifizierung in Go war nicht allzu schwierig, da NextAuth im Backend relativ einfach nachzuahmen war. NextAuth (jetzt Auth.js) verfügt über APIs zur Überprüfung einer Sitzung.
Das bedeutete, dass wir einen TypeScript-Client im Frontend haben konnten, der aus unserer OpenAPI-Spezifikation generiert wurde und Abrufaufrufe an die Backend-API durchführte. Die Anmeldeinformationen werden automatisch in den Abrufaufruf einbezogen und das Backend kann die Sitzung mit NextAuth überprüfen.
4. Testbarkeit
Das Schreiben jeglicher Art von Tests in Go ist sehr einfach und es gibt viele Beispiele, die das Thema Testen von HTTP-Handlern abdecken.
Im Vergleich zur Next.js-API ist es auch viel einfacher, Tests für die neuen Go-API-Endpunkte zu schreiben, insbesondere weil wir authentifizierte Status- und echte Datenbankaufrufe testen möchten. Mit Testcontainern konnten wir problemlos Tests für den Gin-Router schreiben und echte Integrationstests für unsere Postgres-Datenbank starten.
Alles zusammenfügen
Schreiben der OpenAPI-Spezifikation
Wir begannen mit dem Schreiben der OpenAPI 3.0-Spezifikation für unsere API. Ein OpenAPI-First-Ansatz fördert die Gestaltung des API-Vertrags vor der Implementierung und stellt sicher, dass sich alle Beteiligten (Entwickler, Produktmanager und Kunden) über das Verhalten und die Struktur der API einigen, bevor Code geschrieben wird. Es fördert eine sorgfältige Planung und führt zu einem gut durchdachten API-Design, das konsistent ist und etablierten Best Practices entspricht. Aus diesen Gründen haben wir uns dafür entschieden, zuerst die Spezifikation zu schreiben und daraus den Code zu generieren, und nicht umgekehrt.
Mein bevorzugtes Tool hierfür warAPI Fiddle, mit dem Sie schnell OpenAPI-Spezifikationen entwerfen und testen können. Allerdings unterstützt API Fiddle nur OpenAPI 3.1 (das wir nicht verwenden konnten, weil viele Bibliotheken es nicht übernommen hatten), also sind wir bei Version 3.0 geblieben und haben die Spezifikation von Hand geschrieben.
Hier ist ein Beispiel dafür, wie die Spezifikation für unsere API aussah:
openapi: 3.0.0 info: title: Arcjet Sites API description: A CRUD API to manage sites. version: 1.0.0 servers: - url: <https:> 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 </https:>
Nachdem die OpenAPI-Spezifikation vorhanden war, verwendeten wir OAPI-codegen, ein Tool, das automatisch Go-Code aus der OpenAPI-Spezifikation generiert. Es generiert alle notwendigen Typen, Handler und Fehlerbehandlungsstrukturen, was den Entwicklungsprozess wesentlich reibungsloser macht.
//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml ../../api.yaml
Die Ausgabe bestand aus einer Reihe von Go-Dateien, von denen eine das Servergerüst und eine andere die Handler-Implementierungen enthielt. Hier ist ein Beispiel für einen Go-Typ, der für das Site-Objekt generiert wurde:
// 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"` }
Mit dem generierten Code konnten wir die API-Handler-Logik wie folgt implementieren:
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) }
Die Datenbank: Abkehr von DrizzleORM
Drizzle ist ein großartiges ORM für JS-Projekte und wir würden es wieder verwenden, aber die Verschiebung des Datenbankcodes aus Next.js bedeutete, dass wir etwas Ähnliches für Go brauchten.
Wir haben GORM als unser ORM ausgewählt und das Repository-Muster verwendet, um Datenbankinteraktionen zu abstrahieren. Dadurch konnten wir saubere, testbare Datenbankabfragen schreiben.
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 }
Testen. Alles
Testen ist für uns von entscheidender Bedeutung. Wir wollten sicherstellen, dass alle Datenbankaufrufe ordnungsgemäß getestet wurden, deshalb haben wir Testcontainer verwendet, um eine echte Datenbank für unsere Tests aufzubauen, die unserem Produktionsaufbau genau entspricht.
openapi: 3.0.0 info: title: Arcjet Sites API description: A CRUD API to manage sites. version: 1.0.0 servers: - url: <https:> 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 </https:>
Nach dem Einrichten der Testumgebung haben wir alle CRUD-Vorgänge wie in der Produktion getestet, um sicherzustellen, dass sich unser Code korrekt verhält.
//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=config.yaml ../../api.yaml
Zum Testen unserer API-Handler haben wir das httptest-Paket von Go verwendet und die Datenbankinteraktionen mithilfe von Mockery simuliert. Dadurch konnten wir uns auf das Testen der API-Logik konzentrieren, ohne uns um Datenbankprobleme kümmern zu müssen.
// 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"` }
Frontend-Nutzung: OpenAPI-gesteuertes Frontend
Nachdem unsere API getestet und bereitgestellt wurde, richteten wir unsere Aufmerksamkeit auf das Frontend.
Unsere vorherigen API-Aufrufe erfolgten über den von Next.js empfohlenen Abruf-API mit integriertem Caching. Für dynamischere Ansichten verwendeten einige Komponenten zusätzlich zum Abruf SWR könnte typsichere, automatisch nachladende Datenabrufaufrufe erhalten.
Um die API im Frontend zu nutzen, haben wir die Bibliothek openapi-typescript verwendet, die TypeScript-Typen basierend auf unserem OpenAPI-Schema generiert. Dies machte es einfach, das Backend mit unserem Frontend zu integrieren, ohne Datenmodelle manuell synchronisieren zu müssen. Darin ist Tanstack Query integriert, das Fetch unter der Haube nutzt, aber auch mit unserem Schema synchronisiert wird.
Wir migrieren die API-Endpunkte schrittweise auf den neuen Go-Server und nehmen dabei kleine Verbesserungen vor. Wenn Sie Ihren Browser-Inspektor öffnen, sehen Sie, dass diese neuen Anfragen an api.arcjet.com gehen
Screenshot des Browser-Inspektors, der einen API-Aufruf an das neue Go-Backend zeigt.
Die goldene Checkliste
Haben wir also die schwer fassbare Golden API erreicht? Aktivieren wir das Kontrollkästchen:
- ✅ Leistung und Skalierbarkeit – Die API wird auf unseren bestehenden k8s-Clustern bereitgestellt, die bereits auf Leistung abgestimmt sind. Wir verfügen über detaillierte Metriken und können diese bei Bedarf skalieren.
- ✅ Umfassende und klare Dokumentation – Die OpenAPI-Spezifikation bietet eine einzige Quelle der Wahrheit, da Code daraus generiert wird und nicht umgekehrt. Durch die Verwendung generierter Clients ist es für unser Team einfach, mit den APIs zu arbeiten.
- ✅ Sicherheit und Authentifizierung – Wir implementieren Go bereits in der Produktion, damit wir unsere Sicherheitspraktiken kopieren können. Die Authentifizierung erfolgt über NextAuth.
- ✅ Testbarkeit – Wir haben Unit-Tests der Handler und Integrationstests mithilfe von Testcontainern implementiert, eine deutliche Verbesserung gegenüber unseren Next.js-APIs.
Wir gingen weiter mit:
- ✅ Überwachung – Die Bereitstellung auf den vorhandenen k8s-Clustern bedeutete, dass wir die bereits eingerichteten Ablaufverfolgungs-, Protokollierungs- und Metrikfunktionen übernommen haben. Das Hinzufügen von OpenTelemetry-Instrumenten zu Gin war trivial.
- ✅ Einfachheit – Go wurde ursprünglich für APIs entwickelt, und das merkt man. Unser Code ist viel sauberer und wartbarer.
Am Ende sind wir mit den Ergebnissen zufrieden. Unsere API ist schneller, sicherer und besser getestet. Der Übergang zu Go hat sich gelohnt und wir sind jetzt besser in der Lage, unsere API zu skalieren und zu pflegen, wenn unser Produkt wächst.
Das obige ist der detaillierte Inhalt vonÜberdenken unserer REST-API: Aufbau der Golden API. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Dieser Artikel erläutert die Paketimportmechanismen von Go: benannte Importe (z. B. importieren & quot; fmt & quot;) und leere Importe (z. B. Import _ & quot; fmt & quot;). Benannte Importe machen Paketinhalte zugänglich, während leere Importe nur T ausführen

Dieser Artikel beschreibt die effiziente Konvertierung von MySQL -Abfrageergebnissen in GO -Strukturscheiben. Es wird unter Verwendung der SCAN -Methode von Datenbank/SQL zur optimalen Leistung hervorgehoben, wobei die manuelle Parsen vermieden wird. Best Practices für die Struktur -Feldzuordnung mithilfe von DB -Tags und Robus

In diesem Artikel werden die Newflash () -Funktion von BeEGO für die Übertragung zwischen PAGE in Webanwendungen erläutert. Es konzentriert sich auf die Verwendung von Newflash (), um temporäre Nachrichten (Erfolg, Fehler, Warnung) zwischen den Controllern anzuzeigen und den Sitzungsmechanismus zu nutzen. Limita

In diesem Artikel werden die benutzerdefinierten Typ -Einschränkungen von GO für Generika untersucht. Es wird beschrieben, wie Schnittstellen die minimalen Typanforderungen für generische Funktionen definieren und die Sicherheitstypsicherheit und die Wiederverwendbarkeit von Code verbessern. Der Artikel erörtert auch Einschränkungen und Best Practices

Dieser Artikel zeigt, dass Mocks und Stubs in GO für Unit -Tests erstellen. Es betont die Verwendung von Schnittstellen, liefert Beispiele für Mock -Implementierungen und diskutiert Best Practices wie die Fokussierung von Mocks und die Verwendung von Assertion -Bibliotheken. Die Articl

Dieser Artikel beschreibt effizientes Dateischreiben in Go und vergleicht OS.WriteFile (geeignet für kleine Dateien) mit OS.openfile und gepufferter Schreibvorgänge (optimal für große Dateien). Es betont eine robuste Fehlerbehandlung, die Verwendung von Aufschub und Überprüfung auf bestimmte Fehler.

In dem Artikel werden Schreiben von Unit -Tests in GO erörtert, die Best Practices, Spottechniken und Tools für ein effizientes Testmanagement abdecken.

In diesem Artikel wird die Verwendung von Tracing -Tools zur Analyse von GO -Anwendungsausführungsfluss untersucht. Es werden manuelle und automatische Instrumentierungstechniken, den Vergleich von Tools wie Jaeger, Zipkin und Opentelemetrie erörtert und die effektive Datenvisualisierung hervorheben


Heiße KI -Werkzeuge

Undresser.AI Undress
KI-gestützte App zum Erstellen realistischer Aktfotos

AI Clothes Remover
Online-KI-Tool zum Entfernen von Kleidung aus Fotos.

Undress AI Tool
Ausziehbilder kostenlos

Clothoff.io
KI-Kleiderentferner

AI Hentai Generator
Erstellen Sie kostenlos Ai Hentai.

Heißer Artikel

Heiße Werkzeuge

DVWA
Damn Vulnerable Web App (DVWA) ist eine PHP/MySQL-Webanwendung, die sehr anfällig ist. Seine Hauptziele bestehen darin, Sicherheitsexperten dabei zu helfen, ihre Fähigkeiten und Tools in einem rechtlichen Umfeld zu testen, Webentwicklern dabei zu helfen, den Prozess der Sicherung von Webanwendungen besser zu verstehen, und Lehrern/Schülern dabei zu helfen, in einer Unterrichtsumgebung Webanwendungen zu lehren/lernen Sicherheit. Das Ziel von DVWA besteht darin, einige der häufigsten Web-Schwachstellen über eine einfache und unkomplizierte Benutzeroberfläche mit unterschiedlichen Schwierigkeitsgraden zu üben. Bitte beachten Sie, dass diese Software

Herunterladen der Mac-Version des Atom-Editors
Der beliebteste Open-Source-Editor

Dreamweaver Mac
Visuelle Webentwicklungstools

PHPStorm Mac-Version
Das neueste (2018.2.1) professionelle, integrierte PHP-Entwicklungstool

SecLists
SecLists ist der ultimative Begleiter für Sicherheitstester. Dabei handelt es sich um eine Sammlung verschiedener Arten von Listen, die häufig bei Sicherheitsbewertungen verwendet werden, an einem Ort. SecLists trägt dazu bei, Sicherheitstests effizienter und produktiver zu gestalten, indem es bequem alle Listen bereitstellt, die ein Sicherheitstester benötigen könnte. Zu den Listentypen gehören Benutzernamen, Passwörter, URLs, Fuzzing-Payloads, Muster für vertrauliche Daten, Web-Shells und mehr. Der Tester kann dieses Repository einfach auf einen neuen Testcomputer übertragen und hat dann Zugriff auf alle Arten von Listen, die er benötigt.
