🌐 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

MetodGüvenli?Idempotent?Tipik Kullanım
GETKaynak okuma
HEADBody’siz meta
POST❌*Yeni kaynak/işlem tetikleme
PUTTam güncelleme/yerleştirme
PATCH⚠️Kısmi güncelleme
DELETESilme

* POST idempotent hale, Idempotency-Key ile getirilebilir (aşağıda).

📮 Durum Kodları (Örneklerle)

KodAnlamNot
200 OKBaşarılıGET/PUT/PATCH
201 CreatedKaynak oluşturulduLocation header zorunlu
204 No ContentBody yokDELETE/PUT
304 Not ModifiedKoşullu GETETag/If-None-Match
400 Bad RequestGeçersiz istekValidation hataları
401 UnauthorizedKimlik doğrulama gerekliWWW-Authenticate
403 ForbiddenYetki yokKimlik var, yetki yok
404 Not FoundKaynak yokGizleme için 404
409 ConflictÇakışmaVersiyon/idempotency
415 Unsupported Media Typeİçerik türü hatalıContent-Type
422 Unprocessable EntityAnlamsal doğrulamaDomain kuralı ihlali
429 Too Many RequestsLimit aşıldıRetry-After
5xxSunucu 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 + Location ile 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/Sunset header’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 Conflict veya aynı 201 Created + Location dö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-After ile 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/prev URL’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.