Indledning Hvad er en "kontrakt" i forbindelse med serviceinteraktion? I forbindelse med interagerende servicemoduler opstår det uundgåelige spørgsmål: foregår kommunikationen? I IT-produkter repræsenterer en "kontrakt" en formel forståelse af, der flyder mellem systemer, og de transmitteres. Dette indebærer dataformatet (JSON, Protobuf osv.), strukturelle elementer (felter, datatyper), kommunikationsprotokol (REST, gRPC, beskedkøer) og andre specifikationer. Efter hvilke regler hvilke data hvordan 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). Hvorfor folk har en tendens til at vælge en tabel i databasen som en "kontrakt". 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?" Når alt altid har været gemt i ét DB-system i én virksomhed, hvorfor så genopfinde hjulet? Historisk eller organisatorisk vane: Vi skriver, du vil læse uden at opsætte autorisationsregler og designe API-specifikationer. "Quick fix"-mentaliteten: Når du arbejder med ti eller endda hundredvis af gigabyte data, virker direkte overførsel til en delt tabel enklere, hurtigere og mere økonomisk, men i praksis skaber det problemer med skalerbarhed og ydeevne samt problemer med dataejerskab. "Big data"-argumentet: 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. Hvorfor "en tabel i databasen" ikke er en kontrakt (og hvorfor det er et anti-mønster). Mangel på en klart defineret grænseflade 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. Versions- og evolutionsproblemer 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. ), som er tæt koblet til en specifik DB-motor og kræver omhyggelig håndtering af migreringer. ALTER TABLE 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 : 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. "under-bordet"-aftaler Intet klart ejerskab 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!" Sikkerheds- og adgangskontrolproblemer 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. Afhængighed af indre struktur 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. Smertepunkter og typiske problemer i praksis Koordinering af ændringer Dette er det mest smertefulde aspekt: går man om at informere et andet team om, at skemaet vil ændre sig næste dag? Hvordan Ejeren opretter en ny tabel med et opdateret skema parallelt med det gamle. Den gamle version forbliver tilgængelig for nuværende forbrugere, og ejeren sender dem en besked, der siger: "Den nye struktur er tilgængelig; tjek dokumentationen og deadlines. Migrér venligst, mens begge versioner eksisterer." Et vellykket scenarie for opdatering af tabelversionen: Men i et OLAP-scenarie eller med store datamængder er det ikke en triviel opgave at vedligeholde to parallelle tabeller. Du skal også bestemme, hvordan du flytter data fra det gamle til det nye skema. Dette kan nogle gange kræve planlagt nedetid eller meget sofistikeret infrastruktur. Denne proces introducerer nødvendigvis både risiko og ekstra arbejde. Dataintegritetsproblemer 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. Fejlretnings- og overvågningsudfordringer 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. Single-node fejl trækker alle ned. 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. At levere en separat skrivebeskyttet replika løser ikke problemet. 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: Der er fortsat problemer med versionering. Hovedproblemet er, at når hovedtabelstrukturen ændres, ændres replikaens struktur også, bare med en vis forsinkelse. Replikationsforsinkelse kan få datatilstande til at være uforudsigelige, især med store datasæt. Ejerskab er stadig uklart: Hvem definerer formatet, strukturen og brugsreglerne? En replika er stadig "et stykke" af en andens database. Sådan designes serviceinteraktion korrekt (kontrakt først) En eksplicit kontraktdefinition. 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. Tjeneste som dataejer 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. Implementeringseksempler REST/HTTP: En tjeneste udgiver endepunkter som , osv., og klienter fremsætter anmodninger med et veldefineret dataskema (DTO). GET /items POST /items gRPC / binære protokoller: I gRPC/protobuf er tjenesten og beskederne formelt defineret i .proto-filer, og der foretages blot ændringer i .proto-filerne, hvor metode, anmodning og svar er defineret. Hændelsesdrevet: Dataejertjenesten udgiver begivenheder til en mægler som Kafka eller RabbitMQ, og abonnenter forbruger dem. Kontrakten her er arrangementets format. Strukturelle ændringer foretages gennem versionerede emner eller meddelelser. Versionskontrol Uanset hvilken model, er det både muligt og essentielt at implementere versionskontrol på interfacet. For eksempel: I REST har vi /api/v1/… og /api/v2/. Med gRPC/protobuf er der kraftfulde mekanismer til bagud/fremad kompatibilitet – nye felter, beskeder og metoder kan tilføjes uden at ødelægge gamle klienter, mens andre markeres som forældede. I begivenhedsdrevne arkitekturer kan du udgive gamle og nye begivenhedsformater parallelt, indtil alle forbrugere migrerer. Fordelt ansvar 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. Eksempler på serviceinteraktion Inden for et enkelt team 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. Det er bedre at oprette en "facade" eller "mikroservice", der f.eks. ejer "ordre"-tabellen, og så kalder andre moduler (som analytics) denne facade/tjeneste. Dette holder kontraktprincippet eksplicit og forenkler fejlfinding. 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. Mellem to hold På et højere niveau, når to eller flere hold er ansvarlige for forskellige områder, forbliver principperne de samme. For eksempel: Team A er ansvarlig for produktkatalogtjenesten, der indeholder oplysninger om hver vare (pris, tilgængelighed, attributter). Team B tager sig af indkøbskurvservicen. 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 , 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. GET /catalog/items GET /catalog/items/{id} Organisatoriske aspekter og fordele Gennemsigtig kommunikation 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. Hurtigere udvikling Æ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. Adgangs- og sikkerhedsstyring 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. Konklusion 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 , 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. Contract First 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.