Spring Boot uygulamalarinda API degisikliklerini OpenAPI fark analizi, Spring Cloud Contract stublari ve Spring Security senaryolariyla dogrulayin; spring rest api tuketicilerini kirmadan sozlesme evrimini yonetin.
Spring Boot'ta API Sozlesmesi: Tuketici Testi ve Guvenli Evrim
Microservices mimarisi icinde sozlesme kirilmasini erken yakalamak
Bir microservices mimarisi icinde HTTP 200 donmeye devam eden ama alan anlami degisen endpoint, derleme asamasinda degil production'da hata uretir. Ornegin response'taki state alaninin PAID yerine SETTLED donmesi, Java enum deserialize eden tuketicide hata olusturabilir. Depodaki OpenAPI dosyasini kaynak kodla birlikte surumleyin ve pull request'te farki CI adimi olarak calistirin. Bu disiplin, spring framework egitimi ve java spring egitimi iceriklerinde genellikle gorulen yalnizca controller testi yaklasimindan daha kapsayicidir; cunku ag uzerinden paylasilan semayi test eder.
# Ana branch'teki ve aday degisiklikteki OpenAPI dokumanini karsilastir
curl -fsSL "$BASE_URL/v3/api-docs.yaml" -o openapi-base.yaml
curl -fsSL "http://localhost:8080/v3/api-docs.yaml" -o openapi-candidate.yaml
docker run --rm -v "$PWD:/spec" tufin/oasdiff:1.11.7 breaking /spec/openapi-base.yaml /spec/openapi-candidate.yamlBu komutun mekanik faydasi, silinen operation, response body'sinden cikarilan alan, request'te yeni zorunlu alan ve daraltilan enum gibi kirici degisiklikleri HTTP entegrasyonu kurulmadan raporlamasidir. Ancak OpenAPI'da bir alanin required olmaktan cikarilmasi teknik olarak kirici sayilmayabilir; buna ragmen sizin Java istemciniz primitive long kullaniyorsa null deger sessizce 0'a donusebilir. Bu nedenle DTO'larda anlamsiz varsayilanlari ayiklamak icin Long, Boolean ve Bean Validation kullanin.
Spring Cloud Contract ile uretici davranisini executable hale getirmek
Spring Cloud Contract, OpenAPI'nin sema seviyesinde yakalayamadigi davranis detaylarini executable contract olarak saklar: belirli request'e hangi status, header ve body gelecegi test koduna donusur. Uretici servisinde contract'i src/test/resources/contracts altinda tutun. Contract'a sadece tuketicinin gercekten dayandigi alanlari koyun; tum response body'sini literal yazmak, opsiyonel bir metadata alani eklendiginde gereksiz contract kirilmasina neden olur.
import org.springframework.cloud.contract.spec.Contract
Contract.make {
description 'Odendi durumundaki fatura satirlariyla doner'
request {
method GET()
urlPath('/v1/invoices/42') {
queryParameters {
parameter 'expand': 'lines'
}
}
headers {
header 'Authorization', $(consumer(regex('Bearer .+')), producer('Bearer test-token'))
}
}
response {
status OK()
headers {
contentType(applicationJson())
}
body([
id: 42,
state: 'PAID',
lines: [[sku: 'BOOK-1', quantity: 2]]
])
}
}Maven ile generated verification testlerini provider'in normal test paketine dahil edin; aksi halde stub uretilebilir fakat uretici davranisinin contract'a uydugu kanitlanmaz. spring-cloud-starter-contract-verifier bagimliligini test scope'unda, spring-cloud-contract-maven-plugin eklentisini build asamasinda kullanin ve ./mvnw verify komutunu merge kapisi yapin. Kritik incelik: producer tarafindaki Authorization degerini contract'ta sabitlemeyin. Yukaridaki regex, token formatini dogrular; gercek imza denetimini ise Spring Security entegrasyon testine birakir.
Spring Boot tuketicisinde stub tabanli sozlesme testi
Tuketicinin provider'a yaptigi cagrilari sadece MockWebServer ile elde yazilmis fixture'lara karsilik test etmek, zamanla provider gerceginden kopan fixture biriktirir. Bunun yerine Spring Cloud Contract'in provider tarafinda uretilmis stub JAR'ini calistirin. Bir spring boot egitimi veya spring boot kursu projesinde bu ayrim ozellikle degerlidir: client kodunun varsayimlarini provider'in yayinladigi artifact uzerinden test edersiniz.
@SpringBootTest
@AutoConfigureStubRunner(
ids = "com.acme:billing-service:1.8.4:stubs:8089",
stubsMode = StubRunnerProperties.StubsMode.LOCAL)
class BillingClientContractTest {
private final RestClient client = RestClient.builder()
.baseUrl("http://localhost:8089")
.build();
@Test
void paidInvoiceIsMappedWithoutLosingLines() {
InvoiceDto invoice = client.get()
.uri("/v1/invoices/{id}?expand=lines", 42)
.header(HttpHeaders.AUTHORIZATION, "Bearer local-test-token")
.retrieve()
.body(InvoiceDto.class);
assertThat(invoice.id()).isEqualTo(42L);
assertThat(invoice.state()).isEqualTo(InvoiceState.PAID);
assertThat(invoice.lines()).singleElement()
.satisfies(line -> assertThat(line.sku()).isEqualTo("BOOK-1"));
}
}CI'da LOCAL yerine artifact repository kullanan bir stub modu tercih edin ve contract artifact surumunu sabitleyin. Dinamik + surumu, testin bugun bir stub'a yarin baska bir stub'a karsilik calismasina yol acar. Ayrica RestClient.retrieve() varsayilan olarak 4xx ve 5xx durumlarini exception'a cevirir; 404'ten bos sonuc uretecek bir is kurali varsa onStatus ile bunu acikca map edin. Aksi halde test yalnizca mutlu yolu dogrular ve hata semasi degisikliklerini kacirir.
Spring REST API uyumlulugunda OpenAPI ve davranis kurallarini ayirmak
Spring REST API evriminde iki farkli kontrol gerekir. OASDiff sema farkini bulur; Spring Cloud Contract ise ornek request-response davranisini calistirir. Ornegin POST /v1/orders endpoint'ine yeni bir zorunlu currency alani eklemek sema seviyesinde kiricidir. Buna karsin ayni endpoint'in yinelenen isteklerde 201 yerine 409 donmesi, schema ayni kalsa bile davranissal kirilma olabilir. Her ikisini ayri CI job'lari olarak calistirin ki hata raporu degisikligin turunu gostersin.
openapi: 3.0.3
paths:
/v1/orders:
post:
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [customerId, currency]
properties:
customerId:
type: string
currency:
type: string
enum: [TRY, EUR, USD]Enum daraltma edge case'ini ayri inceleyin: provider sadece TRY kabul etmeye basladiginda, eski bir tuketicinin EUR gondermesi 400 ile sonuclanir. Fark analiz aracinin bunu raporlamasi gerekir, ancak request validation'in gercekte devrede oldugunu da test edin. springdoc-openapi sadece dokuman uretir; request'i otomatik validate etmez. Runtime dogrulama icin request DTO'sunda @NotNull ve ozel bir enum deserializer kullanin veya JSON Schema validation filtreleriyle request body'sini kontrol edin.
Spring Security ve Spring MVC hata sozlesmesini test etmek
Yetkilendirme sonucu da API sozlesmesidir. Spring Security konfigurasyonunda authentication yoksa 401, kimligi olan kullanicinin scope'u yetersizse 403 donmesini contract veya MockMvc testiyle sabitleyin. Spring MVC exception handler'inizin 401 body'sini ProblemDetail olarak donmesi, proxy veya mobil istemcinin hatayi tutarli parse etmesini saglar; burada asil mekanizma, AuthenticationEntryPoint ile AccessDeniedHandler'in farkli yollardan cagrilmasidir.
@Test
void missingTokenReturnsProblemDetail401() throws Exception {
mockMvc.perform(get("/v1/invoices/42"))
.andExpect(status().isUnauthorized())
.andExpect(header().string("Content-Type",
containsString("application/problem+json")))
.andExpect(jsonPath("$.status").value(401))
.andExpect(jsonPath("$.type").value(
"https://api.acme.example/problems/unauthenticated"));
}Token claim'lerini contract body'sine koymak yaygin bir hatadir. Subject, expiration ve JWT imzasi ortamlar arasinda degisir. Bunun yerine security contract'inda HTTP sonucu, WWW-Authenticate header'i, ProblemDetail type URI'si ve gerekiyorsa gerekli scope adi yer alsin. Gercek imza, issuer ve clock-skew davranisini ise test issuer ile ayri bir entegrasyon testinde dogrulayin.
CI olcumuyle sozlesme gecisinin etkisini izlemek
Sozlesme kontrollerinin degerini olcmek icin iki metriyi birlikte izleyin: CI'da bulunan kirici degisiklik sayisi ve yayin sonrasi 4xx orani. Actuator ile Prometheus'a aktardiginiz http_server_requests_seconds_count metriginde URI etiketi sabit endpoint sablonu olmali; raw /v1/invoices/42 gibi kimlik iceren URI'ler yuksek cardinality ile metrik altyapisini sisirir. Spring MVC'nin template tabanli uri etiketi uretmesini koruyun.
sum(rate(http_server_requests_seconds_count{
uri="/v1/invoices/{id}",status=~"400|401|403|422"
}[10m]))
/
sum(rate(http_server_requests_seconds_count{
uri="/v1/invoices/{id}"
}[10m]))Degisiklikten once ve sonra ayni endpoint, trafik hacmi ve zaman penceresiyle bu orani karsilastirin. Ornek rollout kurali: yeni response alanini once opsiyonel yayinlayin, tuketici stub testleri alanin okunabildigini gosterdikten sonra alanin zorunlulugunu ayri bir breaking-change release'ine tasiyin. Bu yaklasim, API gateway loglarindaki sadece status koduna dayali gozlemden daha guvenilirdir; cunku contract testi hangi tuketici varsayiminin degistigini commit asamasinda isaretler.
İlgili Eğitim
YTÜSEM İlgili Eğitim
Java Spring Boot ReactJS FullStack Eğitimi (Yıldız Teknik Üniversitesi SEM)
Sık Sorulan Sorular
spring cloud contract provider ve consumer testleri birlikte nasil calistirilir?
Provider projesinde contract dosyalarini src/test/resources/contracts altina koyun, spring-cloud-contract-maven-plugin ile ./mvnw verify calistirin ve uretilen stubs artifact'ini repository'ye yayinlayin. Consumer projesinde @AutoConfigureStubRunner ile ayni artifact'in sabit surumunu calistirip gercek HTTP client mapping'ini test edin.
spring rest api OpenAPI degisikliginin kirici oldugunu nasil anlarim?
Ana branch ve aday branch OpenAPI dokumanlarini oasdiff breaking komutuyla karsilastirin. Yeni required request alani, silinen response property veya daraltilmis enum sonucu CI'yi fail edin. Buna ek olarak, idempotency ya da hata statusu gibi OpenAPI'nin tek basina ifade edemeyecegi davranislari Spring Cloud Contract senaryosuna yazin.
spring security 401 ve 403 cevaplari neden ayri sozlesme olarak test edilmeli?
401, AuthenticationEntryPoint tarafindan kimlik dogrulama olmadiginda; 403 ise AccessDeniedHandler tarafindan yetki yetersiz oldugunda uretilir. MockMvc testiyle her iki durumun status, application/problem+json Content-Type ve ProblemDetail type URI degerlerini assert edin. Boylece filter chain veya exception handler degisikligi hata formatini sessizce bozmaz.
spring mvc metriklerinde URI cardinality nasil kontrol edilir?
Prometheus sorgularinda /v1/invoices/{id} gibi route template etiketlerini kullanin; ham kimlik degeri iceren URI'leri tag olarak yazmayin. http_server_requests_seconds_count metrigi uzerinden 4xx oranini 10 dakikalik pencerede hesaplayin ve contract rollout oncesi-sonrasi ayni trafik sinifinda karsilastirin.
AI / LLM Discovery
Bu makale Opendart Akademi Spring Framework eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.


