🌐 Web Servisler — Öğrenme & Mülakat Rehberi
HTTP’nin temelleri, REST/SOAP farkları, Spring & JAX-RS/JAX-WS örnekleri, JSON/XML, versiyonlama, HATEOAS, idempotency, timeouts/retries ve üretim ortamı pratikleri bu sayfada.
🔎 Genel Bakış
- HTTP Semantik: metodlar, durum kodları, header’lar, cache davranışları.
- REST: kaynak odaklı tasarım, DTO, content negotiation, HATEOAS, idempotency.
- SOAP: sözleşme (WSDL) önceliklidir; JAX-WS, XML Schema, Fault.
- Üretim Dayanıklılığı: timeouts, retry, circuit breaker, rate limit, izleme.
🧭 HTTP Temelleri
📌 Metod Semantiği
| Metod | Güvenli? | Idempotent? | Tipik Kullanım |
|---|---|---|---|
| GET | ✅ | ✅ | Kaynak okuma |
| HEAD | ✅ | ✅ | Body’siz meta |
| POST | ❌ | ❌* | Yeni kaynak/işlem tetikleme |
| PUT | ❌ | ✅ | Tam güncelleme/yerleştirme |
| PATCH | ❌ | ⚠️ | Kısmi güncelleme |
| DELETE | ❌ | ✅ | Silme |
* POST idempotent hale, Idempotency-Key ile getirilebilir (aşağıda).
📮 Durum Kodları (Örneklerle)
| Kod | Anlam | Not |
|---|---|---|
| 200 OK | Başarılı | GET/PUT/PATCH |
| 201 Created | Kaynak oluşturuldu | Location header zorunlu |
| 204 No Content | Body yok | DELETE/PUT |
| 304 Not Modified | Koşullu GET | ETag/If-None-Match |
| 400 Bad Request | Geçersiz istek | Validation hataları |
| 401 Unauthorized | Kimlik doğrulama gerekli | WWW-Authenticate |
| 403 Forbidden | Yetki yok | Kimlik var, yetki yok |
| 404 Not Found | Kaynak yok | Gizleme için 404 |
| 409 Conflict | Çakışma | Versiyon/idempotency |
| 415 Unsupported Media Type | İçerik türü hatalı | Content-Type |
| 422 Unprocessable Entity | Anlamsal doğrulama | Domain kuralı ihlali |
| 429 Too Many Requests | Limit aşıldı | Retry-After |
| 5xx | Sunucu hatası | Retry politikasına aday |
📦 Temel Header’lar
Accept,Content-Type→ içerik pazarlığı.Authorization: Bearer <jwt>→ stateless auth.Cache-Control,ETag,If-None-Match→ cache & koşullu istek.Location→ 201 sonrası yeni kaynağın URI’si.
GET /api/users/42 HTTP/1.1
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
ETag: "u-42-v7"
{"id":42,"username":"neo"}
☕ REST (Spring & JAX-RS)
🎯 Kaynak Tasarımı
- Çoğul isimler:
/users,/orders/{id}. - Filtreleme:
/orders?status=PAID&from=2024-01-01 - İlişkiler:
/users/{id}/orders - Durum makineleri için durum değişiklik uçları ya da komut kaynakları düşün.
// Spring Boot örneği
@RestController
@RequestMapping(value="/api/users", produces="application/json")
@RequiredArgsConstructor
public class UserController {
private final UserService service;
@GetMapping public Page<UserView> list(Pageable p){ return service.list(p); }
@PostMapping(consumes="application/json")
public ResponseEntity<UserView> create(@Valid @RequestBody UserCreate dto){
UserView v = service.create(dto);
return ResponseEntity.created(URI.create("/api/users/" + v.id())).body(v);
}
@PutMapping("/{id}") public UserView update(@PathVariable Long id, @RequestBody UserUpdate dto){ return service.update(id, dto); }
@DeleteMapping("/{id}") public ResponseEntity<Void> delete(@PathVariable Long id){ service.delete(id); return ResponseEntity.noContent().build(); }
}
// JAX-RS (Jersey) örneği
@Path("/users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UserResource {
@GET public Response list(@BeanParam PageParam p){ /* ... */ return Response.ok(...).build(); }
@POST
public Response create(UserCreate dto, @Context UriInfo uri) {
var v = /* service.create */ dto;
URI location = uri.getAbsolutePathBuilder().path(String.valueOf(v.id())).build();
return Response.created(location).entity(v).build();
}
}
🪄 REST İpuçları
- DTO kullan (Entity sızdırma).
- 201 +
Locationile oluşturma semantiğini koru. - PUT idempotent, PATCH kısmi değişim; sürüm/kaynak çakışmasında 409.
🧼 SOAP (JAX-WS)
📐 Temeller
- WSDL sözleşmesi: tip güvenliği, XSD şemaları.
- SOAP Envelope/Body/Header; Fault ile hata taşır.
- Genellikle enterprise entegrasyon, transaction’lı işlemler, kuyruk uyumu.
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:usr="http://example.com/user">
<soapenv:Header/>
<soapenv:Body>
<usr:getUser><id>42</id></usr:getUser>
</soapenv:Body>
</soapenv:Envelope>
// JAX-WS örneği
import jakarta.jws.*;
@WebService(targetNamespace="http://example.com/user")
public class UserEndpoint {
@WebMethod
public UserDto getUser(@WebParam(name="id") long id) { /* ... */ return new UserDto(id,"neo"); }
}
🧩 SOAP vs REST
- REST hafif & HTTP semantiğine doğal; SOAP sözleşme + XML + WS-* özellikleri.
- Güçlü tip kontratı lazım ise SOAP; esneklik/perf için REST.
📦 JSON & XML
🧰 İçerik Türleri & Dönüşüm
- application/json, application/xml, application/problem+json.
- Spring: HttpMessageConverter’lar (Jackson, JAXB).
{
"id": 42,
"username": "neo",
"links": [{"rel":"self","href":"/api/users/42"}]
}
<user id="42">
<username>neo</username>
<links><link rel="self" href="/api/users/42"/></links>
</user>
// Jackson & JAXB notları
public record UserView(Long id, String username) {}
@XmlRootElement(name="user")
public class UserXml { public long id; public String username; /* getter/setter */ }
🏷️ API Versiyonlama
🔢 Stratejiler
- Path:
/v1/users(en yaygın, keşfi kolay) - Header:
Accept: application/vnd.company.v2+json - Query:
?v=2(daha az önerilir)
// Spring: farklı controller ile V1/V2
@RestController @RequestMapping("/api/v1/users") class UserV1 { /* ... */ }
@RestController @RequestMapping("/api/v2/users") class UserV2 { /* ... */ }
🪄 Versiyonlama İpuçları
- Geriye uyumluluk kırılıyorsa yeni sürüm aç.
- Deprecation süreci:
Deprecation/Sunsetheader’ları ve dokümantasyon.
🧩 HATEOAS
🔗 Linklerle Keşfedilebilirlik
HATEOAS, istemcinin sonraki aksiyonları link’lerle keşfetmesini sağlar.
// Spring HATEOAS
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
public EntityModel<UserView> toModel(UserView v){
return EntityModel.of(v)
.add(linkTo(methodOn(UserController.class).get(v.id(), false)).withSelfRel())
.add(linkTo(methodOn(UserController.class).list(PageRequest.of(0,20))).withRel("list"));
}
{
"id": 42,
"username": "neo",
"_links": {
"self": {"href": "/api/users/42"},
"list": {"href": "/api/users?page=0&size=20"}
}
}
🔁 Idempotency
🧠 Kavram
- GET/PUT/DELETE doğası gereği idempotent olmalı.
- POST, Idempotency-Key ile idempotent hale getirilebilir.
POST /api/payments
Idempotency-Key: 7d1f-...-c9a1
Content-Type: application/json
{"orderId":123,"amount":100.00}
// Spring Filtresi (özet fikir)
@Component
public class IdempotencyFilter extends OncePerRequestFilter {
private final IdempotencyStore store; // key -> response cache
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
String key = req.getHeader("Idempotency-Key");
if(key != null && store.exists(key)){ store.writeResponse(key, res); return; }
ContentCachingResponseWrapper wrapper = new ContentCachingResponseWrapper(res);
chain.doFilter(req, wrapper);
if(key != null && wrapper.getStatus() < 500){ store.save(key, wrapper); }
wrapper.copyBodyToResponse();
}
}
🪄 Uygulama Tüyoları
- Key TTL → tekrar denemelerde aynı yanıt.
- Çakışmada
409 Conflictveya aynı201 Created+Locationdöndür.
⏱️ Timeouts, Retries & Circuit Breaker
🧰 Client Timeouts
// Spring WebClient
WebClient client = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(HttpClient.create()
.responseTimeout(Duration.ofSeconds(3))
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 2000)))
.build();
# Resilience4j
resilience4j:
retry:
instances:
order:
max-attempts: 3
wait-duration: 300ms
retry-exceptions: java.io.IOException, org.springframework.web.client.HttpServerErrorException
circuitbreaker:
instances:
order:
sliding-window-size: 20
failure-rate-threshold: 50
wait-duration-in-open-state: 10s
@CircuitBreaker(name="order") @Retry(name="order")
public OrderDto callOrder(Long id){ return client.get().uri("/orders/{id}", id).retrieve().bodyToMono(OrderDto.class).block(); }
- Retry yalnızca güvenli/idempotent işlemlerde.
- Backoff kullan, “thundering herd” yapma.
⚡ HTTP Caching & ETag
🧱 Koşullu GET
GET /api/users/42
If-None-Match: "u-42-v7"
HTTP/1.1 304 Not Modified
// Spring: ETag oluşturma (örnek)
@GetMapping("/users/{id}")
public ResponseEntity<UserView> get(@PathVariable Long id){
UserView u = service.get(id);
String etag = "\"u-" + id + "-v" + u.version() + "\"";
return ResponseEntity.ok().eTag(etag).body(u);
}
Cache-Control: max-age=60, must-revalidate- stale-while-revalidate CDN/Proxy tarafında sık kullanılır.
🛡️ Güvenlik, CORS & Rate Limiting
🔐 Kimlik & Yetki
- API’lerde Bearer JWT yaygın; OAuth2 scope tabanlı yetki.
- Tarayıcıdan çağrılan API’lerde CORS politikaları.
- Tarayıcı stateful uygulamalarda CSRF; stateless JWT’de kapalı tutulabilir.
@Bean
SecurityFilterChain http(HttpSecurity h) throws Exception {
return h.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(a -> a.requestMatchers("/actuator/**").permitAll().anyRequest().authenticated())
.oauth2ResourceServer(o -> o.jwt())
.build();
}
🚦 Rate Limiting
- 429 +
Retry-Afterile sinyal ver. - Algoritmalar: token bucket, leaky bucket, sliding window.
📑 Sayfalama & Hata Modeli
📄 Sayfalama
- Offset/Limit:
?page=0&size=20— büyük offset’te yavaş. - Keyset (seek) pagination:
?after=2024-01-01T00:00Z,42— hızlı, tutarlı. - Link header’ı ile
next/prevURL’leri gönder.
🚨 Hata Gövdeleri
RFC 7807 application/problem+json formatı:
{
"type": "https://errors.example.com/validation",
"title": "Validation failed",
"status": 400,
"detail": "email must be valid",
"instance": "/api/users"
}
@RestControllerAdvice
class ProblemHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
ResponseEntity<Map<String,Object>> handle(MethodArgumentNotValidException ex){
Map<String,Object> body = Map.of(
"type","https://errors.example.com/validation",
"title","Validation failed",
"status",400,
"detail", ex.getBindingResult().getFieldErrors().get(0).getDefaultMessage()
);
return ResponseEntity.badRequest()
.contentType(MediaType.valueOf("application/problem+json"))
.body(body);
}
}
🧪 Test & Dokümantasyon
🧰 MockMvc / WebTestClient
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired MockMvc mvc;
@Test void shouldCreate() throws Exception {
mvc.perform(post("/api/users").contentType("application/json")
.content("{\"username\":\"neo\",\"email\":\"n@e.o\"}"))
.andExpect(status().isCreated())
.andExpect(header().string("Location", Matchers.containsString("/api/users/")));
}
}
📄 OpenAPI (Swagger)
- springdoc-openapi ile /v3/api-docs ve /swagger-ui.html
- Kontrat-tabanlı geliştirme ve Postman koleksiyonları
openapi: 3.0.3
paths:
/api/users:
post:
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/UserCreate' }
responses:
'201': { description: Created }
components:
schemas:
UserCreate:
type: object
required: [username, email]
properties: { username: {type: string}, email: {type: string, format: email} }
❓ Mülakat Soruları & Kısa Cevaplar
1) PUT ve PATCH farkı?
PUT tam temsili idempotent biçimde yerleştirir; PATCH kısmi değişiklik yapar (idempotency garanti değil).
2) 201 Created ne zaman kullanılır?
Yeni kaynak oluşturulduğunda; Location header ile URI döndürülmeli.
3) Idempotency nasıl sağlanır?
Doğası gereği idempotent metodlar (GET/PUT/DELETE) ve POST için Idempotency-Key + sunucu tarafı kayıt.
4) ETag nedir?
Kaynak sürüm parmak izi; If-None-Match ile 304 dönüşü sağlar (band genişliği tasarrufu).
5) HATEOAS neden önemlidir?
İstemcinin akışları link’ler ile keşfetmesini sağlar; sık değişen akışlarda esneklik.
6) SOAP ile REST farkı?
SOAP sözleşme + XML + WS-*; REST HTTP semantiği odaklı, hafif ve cache dostu.
7) Content negotiation nasıl çalışır?
İstemci Accept gönderir; sunucu uygun türü (Content-Type) seçip döndürür.
8) 429 ile ne göndermelisin?
Retry-After veya pencere bilgisi; rate-limit iletişimi.
9) Timeout ve Retry ilişkisi?
Önce doğru timeoutları belirle; yalnız idempotent/güvenli işlemlerde retry uygula; backoff kullan.
10) API versiyonlama stratejileri?
Path (en pratik), header (medya tipi), query (daha az önerilir); kıran değişiklikte yeni sürüm.
11) 409 Conflict ne zaman?
Kaynak sürüm çakışması, benzersiz kısıt ihlali veya idempotent POST tekrarlarında.
12) CORS nedir?
Farklı origin’den gelen tarayıcı isteklerine sunucu izinlerini (origin, header, metod) beyan eder.