10/10/2024
I en verden domineret af distribuerede systemer er mikroservices-arkitektur blevet en hjørnesten for mange moderne applikationer. Kernen i denne arkitektur ligger i, hvordan de uafhængige services kommunikerer med hinanden. Al dataudveksling, hvad enten det er via beskeder eller API-kald, er afgørende for systemets ydeevne og pålidelighed. Derfor er et gennemtænkt og veludført API-design ikke blot en fordel, men en absolut nødvendighed. Uden effektive API'er risikerer man at skabe langsomme og ustabile systemer, der hurtigt bliver flaskehalse.

Når teams arbejder uafhængigt på forskellige services, er det altafgørende, at API'er har veldefinerede semantikker og versionsstyringsordninger. Dette sikrer, at opdateringer i én service ikke utilsigtet bryder funktionaliteten i andre afhængige services. Vi skal dykke ned i de forskellige aspekter af API-design, der er vitale for at opbygge robuste og skalerbare mikroservices-systemer.
- Offentlige API'er versus Backend API'er: Forskellige Behov
- REST versus RPC: Et Valg med Konsekvenser
- Effektivitet, Serialisering og IDL
- Framework- og Sprogunderstøttelse samt Kompatibilitet
- Baseline Anbefaling
- RESTful API Design: Bedste Praksis
- Mapping af REST til DDD-mønstre
- API-Versionering: Styring af Ændringer
- Idempotente Operationer: Robusthed i Fokus
- Ofte Stillede Spørgsmål (FAQ)
- Hvad er den primære forskel mellem offentlige API'er og backend API'er?
- Hvornår skal jeg vælge REST over HTTP, og hvornår skal jeg overveje gRPC?
- Hvad betyder det, at en operation er idempotent, og hvorfor er det vigtigt?
- Hvad er en IDL, og hvorfor er den nyttig i API-design?
- Hvordan håndterer jeg API-versionering i mikroservices?
- Konklusion
Offentlige API'er versus Backend API'er: Forskellige Behov
Det er vigtigt at skelne mellem to primære typer af API'er, da de har forskellige krav og anvendelsesområder:
- Offentlige API'er: Disse API'er kaldes af klientapplikationer, typisk browserbaserede webapplikationer eller native mobilapplikationer. De skal være brugervenlige, sikre og kompatible med en bred vifte af klienter.
- Backend API'er: Disse API'er bruges til intern kommunikation mellem services inden for mikroservices-arkitekturen. Her er netværksydelse og effektivitet ofte i højsædet, da interservice-kommunikation kan generere en enorm mængde netværkstrafik.
For offentlige API'er er valget ofte REST over HTTP. Dette skyldes REST's kompatibilitet med klientapplikationer, dets velkendte semantik og den brede understøttelse i forskellige sprog og frameworks. For backend API'er kan imidlertid overvejelser som serialiseringshastighed og payload-størrelse blive mere afgørende. Her er alternativer til REST over HTTP, såsom gRPC, Apache Avro og Apache Thrift, populære valg, da de understøtter binær serialisering og generelt er mere effektive.
REST versus RPC: Et Valg med Konsekvenser
Valget mellem en REST-stil grænseflade og en RPC-stil grænseflade er en af de mest fundamentale beslutninger i API-design.
REST (Representational State Transfer)
REST modellerer ressourcer, hvilket ofte er en naturlig måde at udtrykke domænemodellen på. Den definerer en ensartet grænseflade baseret på HTTP-verber (GET, POST, PUT, DELETE), hvilket fremmer udviklingsmuligheder. REST har veldefinerede semantikker med hensyn til idempotens, sideeffekter og svar-koder, og den håndhæver
Fordele ved REST:
- Bred understøttelse og forståelse.
- Enkel at cache.
- Statelessness forbedrer skalerbarhed.
- God til ressourceorienterede operationer.
Ulemper ved REST:
- Kan være mindre effektiv for komplekse operationer eller mange små kald.
- Afhænger af HTTP-protokollen.
RPC (Remote Procedure Call)
RPC er mere orienteret omkring operationer eller kommandoer. Fordi RPC-grænseflader ligner lokale metodekald, kan det føre til design af overdrevent snakkesalige API'er, hvor mange små kald er nødvendige for at udføre en enkelt logisk operation. Dette er dog ikke en iboende fejl ved RPC, men snarere et spørgsmål om omhyggeligt design. Populære RPC-frameworks inkluderer gRPC, Apache Avro og Apache Thrift, som ofte tilbyder bedre ydeevne gennem binær serialisering.
Fordele ved RPC:
- Meget effektiv til specifikke operationer.
- Ofte hurtigere på grund af binære protokoller.
- Stærk typekontrol med IDL.
Ulemper ved RPC:
- Kan føre til strammere kobling mellem services.
- Mindre standardiseret end REST.
Sammenligningstabel: REST vs. RPC
| Egenskab | REST over HTTP (JSON) | RPC (f.eks. gRPC) |
|---|---|---|
| Paradigme | Ressourceorienteret | Operation/Kommando-orienteret |
| Protokol | HTTP | Flere (f.eks. HTTP/2 for gRPC) |
| Dataformat | Tekstbaseret (JSON, XML) | Binær (Protocol Buffers, Avro) |
| Effektivitet | God, men kan være overhead | Meget høj, lav overhead |
| Kompleksitet | Enkel at forstå og implementere | Kræver ofte IDL og kode-generering |
| Brugsscenarie | Offentlige API'er, CRUD-operationer | Backend-kommunikation, højtydende opkald |
Effektivitet, Serialisering og IDL
Når man vælger en API-implementering, er effektivitet i form af hastighed, hukommelse og payload-størrelse afgørende. Typisk er en gRPC-baseret grænseflade hurtigere end REST over HTTP, primært på grund af dens brug af binære serialiseringsformater som Protocol Buffers. Serialisering handler om, hvordan objekter konverteres til et format, der kan sendes over netværket. Tekstbaserede formater som JSON er meget interoperable, da de fleste sprog og frameworks understøtter JSON-serialisering. Binære formater er derimod generelt hurtigere, men kræver ofte et fast skema og kompilering af en skemadefinitionsfil, hvilket skal indarbejdes i byggeprocessen.
En Interface Definition Language (IDL) bruges til at definere metoder, parametre og returværdier for en API. En IDL kan bruges til at generere klientkode, serialiseringskode og API-dokumentation, og kan også forbruges af API-testværktøjer. Frameworks som gRPC, Avro og Thrift definerer deres egne IDL-specifikationer. REST over HTTP har ikke et standard IDL-format, men et almindeligt valg er OpenAPI (tidligere Swagger). Selvom du kan oprette en HTTP REST API uden en formel definitionssprog, mister du fordelene ved kodegenerering og test.
Framework- og Sprogunderstøttelse samt Kompatibilitet
HTTP er understøttet i næsten alle frameworks og sprog, hvilket gør REST til et yderst fleksibelt valg. gRPC, Avro og Thrift har alle biblioteker til C++, C#, Java og Python, og Thrift og gRPC understøtter også Go. Hvis du vælger en protokol som gRPC, kan du muligvis have brug for et protokoltranslationslag mellem den offentlige API og backend'en. En API gateway kan udføre denne funktion. Det er også vigtigt at overveje, hvilke protokoller der er kompatible med din service mesh, hvis du bruger en.
Baseline Anbefaling
Vores baseline-anbefaling er at vælge REST over HTTP, medmindre du har et specifikt behov for ydeevnefordele ved en binær protokol. REST over HTTP kræver ingen specielle biblioteker, skaber minimal kobling (da kaldere ikke behøver en klient-stub for at kommunikere med servicen), og der er rige økosystemer af værktøjer til at understøtte skemadefinitioner, test og overvågning. Desuden er HTTP kompatibel med browserklienter, hvilket eliminerer behovet for et protokoltranslationslag mellem klienten og backend'en. Hvis du vælger REST over HTTP, bør du dog udføre ydeevne- og belastningstest tidligt i udviklingsprocessen for at validere, om det præsterer godt nok til dit scenarie.
RESTful API Design: Bedste Praksis
Godt
- Backends for Frontends: Forskellige klienttyper (f.eks. mobilapplikationer og desktop-webbrowsere) kan kræve forskellige payload-størrelser eller interaktionsmønstre. Overvej at bruge Backends for Frontends-mønsteret til at skabe separate backends for hver klient, der eksponerer en optimal grænseflade for den pågældende klient.
- Idempotente Operationer: For operationer med sideeffekter, overvej at gøre dem
idempotente og implementere dem som PUT-metoder. Dette muliggør sikre gentagelser og kan forbedre robustheden. - Asynkrone Semantikker: HTTP-metoder kan have asynkrone semantikker, hvor metoden returnerer et svar med det samme, men servicen udfører operationen asynkront. I så fald bør metoden returnere en HTTP 202-svarkode (Accepted), der indikerer, at anmodningen blev accepteret til behandling, men at behandlingen endnu ikke er afsluttet.
Mapping af REST til DDD-mønstre
Domain-Driven Design (DDD) mønstre som entity, aggregate og value object kan naturligt mappes til REST API'er, selvom de oprindeligt blev formuleret med objektorienterede programmeringskoncepter for øje. I en mikroservices-arkitektur interagerer services via API'er, og en services interne model er isoleret fra andre services.
Eksempler på mapping:
| DDD Koncept | REST Ækvivalent | Eksempel |
|---|---|---|
| Aggregate | Ressource | { "id": "1234", "status": "pending" } for en leverance |
| Identitet (Entity) | URL | https://leveringsservice/leverancer/1234 |
| Child Entities (af Aggregate) | Links (HATEOAS) | { "href": "/leverancer/1234/bekræftelse" } |
| Opdater Value Objects | PUT eller PATCH | PUT https://leveringsservice/leverancer/1234/aflevering |
| Repository | Samling | https://leveringsservice/leverancer?status=pending |
Når du designer dine API'er, tænk over, hvordan de udtrykker domænemodellen, ikke kun dataene i modellen, men også forretningsoperationerne og begrænsningerne på dataene.

API-Versionering: Styring af Ændringer
En API er en kontrakt mellem en service og dens klienter. Hvis en API ændres, er der risiko for at bryde klienter, der er afhængige af API'en. Derfor er det en god idé at minimere antallet af API-ændringer. Når det er muligt, bør API-ændringer være bagudkompatible. For eksempel bør man undgå at fjerne et felt fra en model, da det kan bryde klienter, der forventer feltet. Tilføjelse af et felt bryder derimod ikke kompatibiliteten, da klienter bør ignorere felter, de ikke forstår i et svar.
Støt
Der er en omkostning ved at understøtte flere versioner i form af udviklingstid, test og operationel overhead. Derfor er det godt at afskrive gamle versioner så hurtigt som muligt. For interne API'er kan teamet, der ejer API'en, arbejde sammen med andre teams for at hjælpe dem med at migrere til den nye version. For eksterne (offentlige) API'er kan det være sværere at afskrive en API-version, især hvis API'en bruges af tredjeparter eller native klientapplikationer.
Overvej at bruge semantisk versionering (MAJOR.MINOR.PATCH) for serviceversioner. Klienter bør dog kun vælge en API ud fra hovedversionsnummeret, eller eventuelt minor-versionen, hvis der er væsentlige (men ikke-brydende) ændringer mellem minor-versioner. At tillade for stor granularitet risikerer at skulle understøtte en udbredelse af versioner.
Idempotente Operationer: Robusthed i Fokus
En operation er idempotent, hvis den kan kaldes flere gange uden at producere yderligere sideeffekter efter det første kald. Idempotens kan være en nyttig robusthedsstrategi, fordi det giver en upstream-service mulighed for sikkert at kalde en operation flere gange. HTTP-specifikationen angiver, at GET-, PUT- og DELETE-metoder skal være
Det er vigtigt at forstå forskellen mellem PUT- og POST-semantik, når man opretter en ny entitet:
- POST: URI'en repræsenterer en overordnet ressource for den nye entitet (f.eks. en samling). Serveren opretter entiteten og tildeler den en ny URI. Hver gang klienten sender en anmodning, opretter serveren en ny entitet med en ny URI.
- PUT: URI'en identificerer entiteten. Hvis der allerede eksisterer en entitet med den URI, erstatter serveren den eksisterende entitet med versionen i anmodningen. Hvis der ikke eksisterer en entitet med den URI, opretter serveren en. Dette gør PUT idempotent for oprettelse, da gentagne kald til samme URI vil opdatere eller genoprette den samme ressource.
Idempotens er afgørende for at håndtere netværksfejl og timeouts i distribuerede systemer, da det tillader sikre genforsøg af operationer.
Ofte Stillede Spørgsmål (FAQ)
Hvad er den primære forskel mellem offentlige API'er og backend API'er?
Offentlige API'er er designet til eksterne klienter (browsere, mobilapps) med fokus på brugervenlighed, sikkerhed og kompatibilitet. Backend API'er er til intern service-til-service kommunikation, hvor effektivitet, lav latenstid og payload-størrelse ofte er vigtigere.
Hvornår skal jeg vælge REST over HTTP, og hvornår skal jeg overveje gRPC?
Vælg REST over HTTP som standard, medmindre du har specifikke krav til høj ydeevne og effektivitet, som gRPC kan levere med sin binære serialisering og brug af HTTP/2. REST er mere interoperabelt og har et bredere økosystem af værktøjer.
Hvad betyder det, at en operation er idempotent, og hvorfor er det vigtigt?
En idempotent operation kan udføres flere gange uden at forårsage yderligere sideeffekter efter det første vellykkede kald. Dette er afgørende for systemets robusthed, da det muliggør sikre genforsøg af operationer i tilfælde af netværksfejl eller timeouts.
Hvad er en IDL, og hvorfor er den nyttig i API-design?
En Interface Definition Language (IDL) bruges til at formelt definere strukturen og adfærden af en API. Den er nyttig, fordi den kan bruges til automatisk at generere klientkode, serialiseringskode og API-dokumentation, hvilket reducerer manuelt arbejde og risikoen for fejl.
Hvordan håndterer jeg API-versionering i mikroservices?
Du bør understøtte versionering i din API-kontrakt. Ved breaking changes introduceres en ny API-version, mens den gamle version fortsat understøttes i en overgangsperiode. Dette kan gøres ved at have flere versioner af API'en i samme service eller ved at køre separate serviceinstanser for hver version. Semantisk versionering anbefales til serviceversioner.
Konklusion
Effektivt API-design er fundamentalt for succesfulde mikroservices-arkitekturer. Ved at forstå forskellen mellem offentlige og backend API'er, afveje fordele og ulemper ved REST og RPC, optimere for effektivitet med passende serialisering og IDL, samt implementere robuste strategier for versionering og
Hvis du vil læse andre artikler, der ligner Effektiv API-Design i Mikroservices-Arkitektur, kan du besøge kategorien Teknologi.
