paint-brush
En tabell som API? Illusioner och verklighetförbi@truewebber
Ny historia

En tabell som API? Illusioner och verklighet

förbi Aleksei Kish9m2025/03/05
Read on Terminal Reader

För länge; Att läsa

Författaren hävdar att användningen av en delad databastabell som ett sätt för tjänst-till-tjänst-kommunikation är ett antimönster. Även om det kan tyckas vara en snabb lösning, leder det till versionshuvudvärk, oklart ägande och svårigheter med skalbarhet och säkerhet. Istället förespråkar artikeln ett "Contract First"-upplägg, där varje tjänst formellt definierar sina gränssnitt och behåller ägandet av sin egen data. Denna metod främjar tydligare ansvarsskyldighet, smidigare utveckling och mer robust integration mellan team.
featured image - En tabell som API? Illusioner och verklighet
Aleksei Kish HackerNoon profile picture
0-item
1-item

Introduktion

Vad är ett "kontrakt" i samband med tjänsteinteraktion?

I samband med interagerande tjänstemoduler uppstår den oundvikliga frågan: Enligt vilka regler sker kommunikation? I IT-produkter representerar ett "kontrakt" en formell förståelse av vilka data som flyter mellan systemen och hur de överförs. Detta innebär dataformat (JSON, Protobuf, etc.), strukturella element (fält, datatyper), kommunikationsprotokoll (REST, gRPC, meddelandeköer) och andra specifikationer.


Ett kontrakt säkerställer öppenhet (alla vet vad som tas emot och skickas), förutsägbarhet (vi kan uppdatera kontraktet och underhålla versioner) och tillförlitlighet (vårt system kommer inte att misslyckas om vi gör välskötta ändringar).

Varför människor tenderar att välja en tabell i databasen som ett "kontrakt".

I praktiken, även om alla pratar om mikrotjänster, "kontrakt" och API:er, ser vi ofta människor anta tillvägagångssättet: "Varför inte skapa en delad tabell i databasen istället för att bygga API:er?"


  • Historisk eller organisatorisk vana: När allt alltid har lagrats i ett DB-system inom ett företag, varför uppfinna hjulet på nytt?


  • "Quick fix"-mentaliteten: Vi skriver, du kommer att läsa utan att ställa in auktoriseringsregler och designa API-specifikationer.


  • "Big data"-argumentet: När man arbetar med tiotals eller till och med hundratals gigabyte data verkar direkt överföring till en delad tabell enklare, snabbare och mer ekonomisk, men i praktiken skapar det skalbarhet och prestandaproblem samt problem med dataägande.


Därför, även om användningen av en delad tabell för datautbyte kan verka effektiv och optimerad för snabba resultat, genererar det olika tekniska och organisatoriska utmaningar på lång sikt. Men när team väljer delade tabeller för datautbyte kan de möta många problem under implementeringen.

Varför "en tabell i databasen" inte är ett kontrakt (och varför det är ett antimönster).

Brist på ett tydligt definierat gränssnitt

När tjänster kommunicerar via REST/gRPC/GraphQL har de en formell definition: OpenAPI (Swagger), protobuf-scheman eller GraphQL-scheman. Dessa definierar i detalj vilka resurser (slutpunkter) som är tillgängliga, vilka fält som förväntas, deras typer och format för begäran/svar. När 'ett delat bord' fungerar som ett kontrakt finns det ingen formell beskrivning: Det finns ingen formell beskrivning av kontraktet; endast tabellschemat (DDL) är tillgängligt och även det är inte väl dokumenterat. Alla mindre ändringar av tabellstrukturen (t.ex. lägga till eller ta bort en kolumn, ändra datatyper) kan påverka andra team som läser från eller skriver till denna tabell.

Versions- och evolutionsproblem

API-versionering är en normal praxis: Vi kanske har v1, v2 och så vidare, och vi kan behålla bakåtkompatibiliteten och sedan gradvis flytta klienter till de nyare versionerna. För databastabeller har vi bara DDL-operationer (t.ex. ALTER TABLE ), som är tätt kopplade till en specifik DB-motor och kräver noggrann hantering av migrering.


Det finns inget centraliserat system som kan skicka varningar till konsumenter om schemaändringar som kräver att de uppdaterar sina frågor. Som ett resultat kan "under-the-table"-affärer uppstå: Någon kan posta i en chatt, "I morgon ändrar vi kolumn X till Y", men det finns ingen garanti för att alla kommer att vara redo i tid.

Inget tydligt ägande

När det finns ett tydligt definierat API är det uppenbart vem som äger det: tjänsten som fungerar som API-utgivare. När flera team använder samma databastabell uppstår förvirring om vem som får bestämma strukturen och vilka fält som ska lagras och hur de ska tolkas. Som ett resultat kan bordet bli "ingens egendom", och varje förändring blir ett uppdrag: "Vi måste kolla med det andra laget ifall de använder den gamla kolumnen!"

Säkerhets- och åtkomstkontrollfrågor

Det är svårt att hålla reda på vem som kan läsa och skriva till en tabell om många lag har tillgång till DB. Det finns en chans att obehöriga tjänster kan komma åt uppgifterna trots att det inte var avsett för dem. Det är lättare att hantera sådana problem med ett API: Du kan kontrollera åtkomsträttigheterna (vem kan anropa vilka metoder), använda autentisering och auktorisering och övervaka vem som ringde vad. Med ett bord är det mycket mer komplicerat.

Beroende av intern struktur

Alla interna ändringar av data (omorganisering av index, partitionering av tabellen, ändring av DB) blir ett globalt problem. Om tabellen fungerar som ett publikt gränssnitt kan ägaren inte göra interna ändringar utan att äventyra alla externa läsare och skribenter.

Smärtpunkter och typiska problem i praktiken

Samordna förändringar

Detta är den mest smärtsamma aspekten: Hur går man tillväga för att informera ett annat team om att schemat kommer att ändras nästa dag?

  • Ett framgångsrikt scenario för uppdatering av tabellversionen: Ägaren skapar en ny tabell med ett uppdaterat schema parallellt med det gamla. Den gamla versionen förblir tillgänglig för nuvarande konsumenter och ägaren skickar ett meddelande till dem som säger: "Den nya strukturen är tillgänglig; kolla in dokumentationen och deadlines. Migrera medan båda versionerna finns."


  • Men i ett OLAP-scenario eller med stora datavolymer är det inte en trivial uppgift att upprätthålla två parallella tabeller. Du måste också bestämma hur du ska flytta data från det gamla till det nya schemat. Detta kan ibland kräva planerad driftstopp eller mycket sofistikerad infrastruktur. Denna process medför med nödvändighet både risk och extraarbete.

Dataintegritetsproblem

När flera lag använder en delad tabell för att välja och uppdatera kritisk data, kan det lätt bli ett "slagfält". Resultatet är att affärslogik hamnar utspridda över olika tjänster och det finns ingen centraliserad kontroll av dataintegriteten. Det blir väldigt svårt att veta varför ett visst fält lagras på ett visst sätt, vem som kan uppdatera det och vad som händer om det lämnas tomt.

Utmaningar för felsökning och övervakning

Anta till exempel att tabellen går sönder: Låt oss säga att det finns dålig data eller att någon har låst några viktiga rader. Att identifiera källan till problemet kan ofta kräva att man ber alla team med DB-åtkomst att avgöra vilken fråga som orsakade problemet. Det är ofta inte uppenbart: Det betyder att ett teams fråga kan ha låst databasen, medan ett annat teams fråga producerar det observerbara felet.

Enkelnodsfel drar ner alla.

En delad databas är en enda felpunkt. Om det går ner, kommer många tjänster att gå ner med det. När databasen har problem med prestanda på grund av en tjänsts tunga frågor upplever alla tjänster problem. I en modell med tydliga API:er och dataägande är varje team mästare på sin tjänsts tillgänglighet och prestanda, så ett fel i en komponent sprids inte till andra.

Att tillhandahålla en separat skrivskyddad replika löser inte problemet.

En vanlig kompromiss är: "Vi ger dig en skrivskyddad replika så att du kan fråga utan att påverka vår huvuddatabas." Till en början kan det lösa vissa belastningsproblem, men:

  • Versionsproblem kvarstår. Huvudproblemet är att när huvudtabellstrukturen ändras, ändras även replikans struktur, bara med en viss fördröjning.


  • Replikeringsfördröjning kan göra att datatillstånd är oförutsägbara, särskilt med stora datamängder.


  • Ägarskap är fortfarande oklart: Vem definierar format, struktur och användningsregler? En replik är fortfarande "en bit" av någon annans databas.

Hur man designar serviceinteraktion på rätt sätt (kontrakt först)

En explicit avtalsdefinition.

Modern designpraxis (till exempel "API First" eller "Contract First") börjar med en formell gränssnittsdefinition. OpenAPI/Swagger-, protobuf- eller GraphQL-scheman används. På så sätt vet både människor och maskiner vilka slutpunkter som är tillgängliga, vilka fält som krävs och vilka datatyper som används.

Tjänst som dataägare

I en mikrotjänster (eller till och med modulär) arkitektur, är antagandet att varje tjänst äger sin data helt. Den definierar struktur, lagring och affärslogik och tillhandahåller ett API för all extern åtkomst till det API. Ingen kan röra "någon annans" databas: bara officiella slutpunkter eller händelser. Detta gör livet lättare närhelst förändringar är i fråga och det är alltid tydligt vem som bär skulden.

Implementeringsexempel

  • REST/HTTP: En tjänst publicerar slutpunkter som GET /items , POST /items , etc., och klienter gör förfrågningar med ett väldefinierat dataschema (DTO).


  • gRPC / binära protokoll: I gRPC/protobuf definieras tjänsten och meddelanden formellt i .proto-filer, och ändringar görs helt enkelt i .proto-filerna där metod, begäran och svar definieras.


  • Händelsestyrd: Den dataägande tjänsten publicerar händelser till en mäklare som Kafka eller RabbitMQ, och prenumeranter konsumerar dem. Kontraktet här är evenemangsformatet. Strukturella förändringar görs genom versionerade ämnen eller meddelanden.

Versionskontroll

Oavsett vilken modell är det både möjligt och väsentligt att implementera versionskontroll på gränssnittet. Till exempel:

  • I REST har vi /api/v1/… och /api/v2/.


  • Med gRPC/protobuf finns det kraftfulla mekanismer för bakåt-/framåtkompatibilitet – nya fält, meddelanden och metoder kan läggas till utan att gamla klienter går sönder medan andra markeras som utfasade.


  • I händelsedrivna arkitekturer kan du publicera gamla och nya eventformat parallellt tills alla konsumenter migrerar.

Utdelat ansvar

En grundläggande princip är att teamet som äger data får bestämma hur de ska lagras och hanteras, men de ska inte ge direkt skrivåtkomst till andra tjänster. Andra måste gå igenom API:t i motsats till att redigera främmande data. Detta ger en tydligare ansvarsfördelning: Om tjänst A är trasig så är det tjänst A:s ansvar att åtgärda det och inte dess grannar.

Exempel på tjänsteinteraktion

Inom ett enda lag

Vid första anblicken, om allt är i ett team, varför komplicera saker med ett API? I verkligheten, även om du har en enda produkt uppdelad i moduler, kan en delad tabell leda till samma problem.


  • Det är bättre att skapa en "fasad" eller "mikrotjänst" som äger tabellen "order", till exempel, och sedan kallar andra moduler (som analytics) denna fasad/tjänst.


  • Detta håller kontraktsprincipen tydlig och förenklar felsökning.


Till exempel är ordertjänsten ägare till ordertabellen och faktureringstjänsten kommer inte direkt åt den tabellen – den ringer till ordertjänstens slutpunkter för att få orderdetaljer eller för att markera en order som betald.

Mellan två lag

På en högre nivå, när två eller flera team ansvarar för olika områden, förblir principerna desamma. Till exempel:

  • Team A ansvarar för produktkatalogtjänsten som innehåller information om varje artikel (pris, tillgänglighet, attribut).


  • Team B sköter varukorgsservicen.


Om Team B direkt frågar "Katalog"-tabellen som tillhör Team A, kan eventuella interna schemaändringar vid A (t.ex. lägga till fält, ändra struktur) påverka Team B.


Det korrekta tillvägagångssättet är att använda ett API: Team A tillhandahåller slutpunkter som GET /catalog/items , GET /catalog/items/{id} etc., och Team B använder dessa metoder. Om A kan stödja äldre och nyare versioner kan de släppa /v2, vilket ger B tid att migrera.

Organisatoriska aspekter och fördelar

Transparent kommunikation

Med ett formellt kontrakt är alla ändringar synliga: i Swagger/OpenAPI, .proto-filer eller händelsedokumentation. Alla uppdateringar kan diskuteras i förväg, testas ordentligt och schemaläggas, med bakåtkompatibilitetsstrategier efter behov.

Snabbare utveckling

Förändringar i en tjänst har mindre inverkan på andra. Teamet behöver inte oroa sig för att "bryta" någon annan om de hanterar nya och gamla fält eller slutpunkter på rätt sätt, vilket säkerställer en smidig övergång.

Åtkomst- och säkerhetshantering

API-gateways, autentisering och auktorisering (JWT, OAuth) är standard för tjänster, men nästan omöjliga med en delad tabell. Det är enklare att finjustera åtkomsten (vem kan anropa vilka metoder), föra loggar, spåra användningsstatistik och införa kvoter. Detta gör systemet säkrare och mer förutsägbart.

Slutsats

En delad tabell i databasen är en implementeringsdetalj snarare än ett avtal mellan tjänster och anses därför inte vara ett kontrakt. De många frågorna (komplex versionshantering, kaotiska förändringar, oklart ägande, säkerhet och prestandarisker) gör detta tillvägagångssätt ohållbart i längden.


Det korrekta tillvägagångssättet är Contract First vilket innebär att definiera interaktion genom formell design och följa principen att varje tjänst förblir ägaren till sina data. Detta bidrar inte bara till att minska tekniska skulder utan ökar också transparensen, snabbar upp produktutvecklingen och möjliggör säkra förändringar utan att behöva engagera sig i brandbekämpning över databasscheman.


Det är både en teknisk fråga (hur man designar och integrerar) och en organisatorisk fråga (hur team kommunicerar och hanterar förändringar). Om du vill att din produkt ska växa utan att behöva hantera oändliga nödsituationer när det gäller databasscheman, bör du börja tänka i termer av kontrakt snarare än direkt databasåtkomst.