I forbindelse med interagerende servicemoduler opstår det uundgåelige spørgsmål: Efter hvilke regler foregår kommunikationen? I IT-produkter repræsenterer en "kontrakt" en formel forståelse af, hvilke data der flyder mellem systemer, og hvordan de transmitteres. Dette indebærer dataformatet (JSON, Protobuf osv.), strukturelle elementer (felter, datatyper), kommunikationsprotokol (REST, gRPC, beskedkøer) og andre specifikationer.
En kontrakt sikrer åbenhed (alle ved, hvad der modtages og sendes), forudsigelighed (vi kan opdatere kontrakten og vedligeholde versioner) og pålidelighed (vores system fejler ikke, hvis vi laver velstyrede ændringer).
I praksis, selvom alle taler om mikrotjenester, "kontrakter" og API'er, ser vi ofte, at folk anvender tilgangen: "Hvorfor ikke oprette en delt tabel i databasen i stedet for at bygge API'er?"
Derfor kan det virke effektivt og optimeret til hurtige resultater at bruge en delt tabel til dataudveksling, men det genererer forskellige tekniske og organisatoriske udfordringer i det lange løb. Men når teams vælger delte tabeller til dataudveksling, kan de stå over for adskillige problemer under implementeringen.
Når tjenester kommunikerer via REST/gRPC/GraphQL, har de en formel definition: OpenAPI (Swagger), protobuf-skemaer eller GraphQL-skemaer. Disse definerer i detaljer, hvilke ressourcer (endepunkter) der er tilgængelige, hvilke felter der forventes, deres typer og anmodnings-/svarformaterne. Når 'et delt bord' fungerer som en kontrakt, er der ikke en formel beskrivelse: Der er ingen formel beskrivelse af kontrakten; kun tabelskemaet (DDL) er tilgængeligt, og selv det er ikke veldokumenteret. Enhver mindre ændring af tabelstrukturen (f.eks. tilføjelse eller sletning af en kolonne, ændring af datatyper) kan påvirke andre hold, der læser fra eller skriver til denne tabel.
API-versionering er en normal praksis: Vi har muligvis v1, v2 og så videre, og vi kan bevare bagudkompatibilitet og derefter gradvist flytte klienter til de nyere versioner. For databasetabeller har vi kun DDL-operationer (f.eks. ALTER TABLE
), som er tæt koblet til en specifik DB-motor og kræver omhyggelig håndtering af migreringer.
Der er intet centraliseret system, der kan sende advarsler til forbrugere om skemaændringer, der kræver, at de opdaterer deres forespørgsler. Som følge heraf kan der forekomme "under-bordet"-aftaler : Nogen kan skrive i en chat, "I morgen ændrer vi kolonne X til Y", men der er ingen garanti for, at alle er klar i tide.
Når der er en klart defineret API, er det tydeligt, hvem der ejer den: tjenesten, der fungerer som API-udgiver. Når flere teams bruger den samme databasetabel, er der forvirring om, hvem der skal bestemme strukturen og hvilke felter, der skal lagres, og hvordan de skal fortolkes. Som et resultat kan bordet blive "ingens ejendom", og hver ændring bliver en søgen: "Vi er nødt til at tjekke med det andet hold, hvis de bruger den gamle kolonne!"
Det er svært at holde styr på, hvem der kan læse og skrive til en tabel, hvis mange hold har adgang til DB. Der er en chance for, at uautoriserede tjenester kan få adgang til dataene, selvom det ikke var beregnet til dem. Det er nemmere at håndtere sådanne problemer med en API: Du kan kontrollere adgangsrettighederne (hvem kan kalde hvilke metoder), bruge godkendelse og autorisation og overvåge, hvem der har kaldt hvad. Med et bord er det meget mere kompliceret.
Alle interne ændringer af dataene (omorganisering af indekser, partitionering af tabellen, ændring af DB) bliver et globalt problem. Hvis tabellen fungerer som en offentlig grænseflade, kan ejeren ikke foretage interne ændringer uden at bringe alle eksterne læsere og skribenter i fare.
Dette er det mest smertefulde aspekt: Hvordan går man om at informere et andet team om, at skemaet vil ændre sig næste dag?
Når flere hold bruger en delt tabel til at vælge og opdatere kritiske data, kan det nemt blive en "slagmark". Resultatet er, at forretningslogikken ender med at blive spredt på tværs af forskellige tjenester, og der er ingen centraliseret kontrol over dataintegriteten. Det bliver meget svært at vide, hvorfor et bestemt felt er gemt på en bestemt måde, hvem der kan opdatere det, og hvad der sker, hvis det efterlades tomt.
Antag for eksempel, at tabellen går i stykker: Lad os sige, at der er dårlige data, eller at nogen har låst nogle vigtige rækker. At identificere kilden til problemet kan ofte kræve, at man beder alle team med DB-adgang om at bestemme, hvilken forespørgsel der forårsagede problemet. Det er ofte ikke indlysende: Det betyder, at et teams forespørgsel kan have låst databasen, mens et andet teams forespørgsel producerer den observerbare fejl.
En delt database er et enkelt fejlpunkt. Hvis det går ned, så vil mange tjenester gå ned med det. Når databasen har problemer med ydeevnen på grund af en tjenestes tunge forespørgsler, oplever alle tjenester problemer. I en model med tydelige API'er og dataejerskab er hvert team mestre over deres tjenestes tilgængelighed og ydeevne, så en fejl i én komponent forplanter sig ikke til andre.
Et almindeligt kompromis er: "Vi giver dig en skrivebeskyttet replika, så du kan forespørge uden at påvirke vores hoveddatabase." I første omgang kan det løse nogle belastningsproblemer, men:
Moderne designpraksis (f.eks. "API First" eller "Contract First") starter med en formel grænsefladedefinition. Der bruges OpenAPI/Swagger-, protobuf- eller GraphQL-skemaer. På denne måde ved både mennesker og maskiner, hvilke endepunkter der er tilgængelige, hvilke felter der er påkrævet, og hvilke datatyper der bruges.
I en mikrotjenester (eller endda modulær) arkitektur er antagelsen, at hver tjeneste ejer sine data fuldstændigt. Den definerer strukturen, lagringen og forretningslogikken og giver en API for al ekstern adgang til denne API. Ingen kan røre ved 'en andens' database: kun officielle slutpunkter eller begivenheder. Dette gør livet lettere, når der er tale om ændringer, og det er altid klart, hvem der har skylden.
GET /items
, POST /items
osv., og klienter fremsætter anmodninger med et veldefineret dataskema (DTO).
Uanset hvilken model, er det både muligt og essentielt at implementere versionskontrol på interfacet. For eksempel:
Et grundlæggende princip er, at det team, der ejer dataene, bestemmer, hvordan de skal opbevares og administreres, men de bør ikke give direkte skriveadgang til andre tjenester. Andre skal gå gennem API'et i modsætning til at redigere udenlandske data. Dette giver en klarere ansvarsfordeling: Hvis service A er i stykker, så er det service A's ansvar at ordne den og ikke dens naboer.
Ved første øjekast, hvis alt er i ét team, hvorfor komplicere tingene med en API? I virkeligheden, selvom du har et enkelt produkt opdelt i moduler, kan en delt tabel føre til de samme problemer.
For eksempel er ordretjenesten ejer af ordretabellen, og faktureringstjenesten får ikke direkte adgang til denne tabel – den foretager opkald til ordretjenestens slutpunkter for at få ordredetaljer eller for at markere en ordre som betalt.
På et højere niveau, når to eller flere hold er ansvarlige for forskellige områder, forbliver principperne de samme. For eksempel:
Hvis Team B direkte forespørger på "Katalog"-tabellen, der tilhører Team A, kan eventuelle interne skemaændringer ved A (f.eks. tilføjelse af felter, ændring af struktur) påvirke Team B.
Den korrekte tilgang er at bruge en API: Team A leverer slutpunkter som GET /catalog/items
, GET /catalog/items/{id}
osv., og Team B bruger disse metoder. Hvis A er i stand til at understøtte ældre og nyere versioner, kan de frigive /v2, hvilket giver B tid til at migrere.
Med en formel kontrakt er alle ændringer synlige: i Swagger/OpenAPI, .proto-filer eller begivenhedsdokumentation. Enhver opdatering kan diskuteres på forhånd, testes korrekt og planlægges med bagudkompatibilitetsstrategier efter behov.
Ændringer i én tjeneste har mindre indflydelse på andre. Teamet behøver ikke at bekymre sig om at "knække" en anden, hvis de administrerer nye og gamle felter eller endepunkter korrekt, hvilket sikrer en glidende overgang.
API-gateways, godkendelse og godkendelse (JWT, OAuth) er standard for tjenester, men næsten umulige med en delt tabel. Det er nemmere at finjustere adgangen (hvem kan kalde hvilke metoder), føre logfiler, spore brugsstatistikker og pålægge kvoter. Dette gør systemet mere sikkert og mere forudsigeligt.
En delt tabel i databasen er en implementeringsdetalje snarere end en aftale mellem tjenester og betragtes derfor ikke som en kontrakt. De mange problemer (kompleks versionering, kaotiske ændringer, uklart ejerskab, sikkerhed og ydeevnerisici) gør denne tilgang uholdbar i det lange løb.
Den korrekte tilgang er Contract First , hvilket betyder at definere interaktion gennem formelt design og følge princippet om, at hver tjeneste forbliver ejeren af sine data. Dette hjælper ikke kun med at mindske teknisk gæld, men øger også gennemsigtigheden, fremskynder produktudviklingen og muliggør sikre ændringer uden at skulle engagere sig i brandslukning over databaseskemaer.
Det er både et teknisk spørgsmål (hvordan man designer og integrerer) og et organisatorisk spørgsmål (hvordan teams kommunikerer og håndterer ændringer). Hvis du vil have dit produkt til at vokse uden at skulle håndtere endeløse nødsituationer vedrørende databaseskemaer, så bør du begynde at tænke i kontrakter frem for direkte databaseadgang.