시냅스

Spring 예제로 보는 중복 Request 방지, GUID (≒ UUID) 본문

Java, Spring

Spring 예제로 보는 중복 Request 방지, GUID (≒ UUID)

ted k 2023. 3. 2. 21:56

 

https://www.techtarget.com/searchwindowsserver/definition/GUID-global-unique-identifier

 

GUID

GUID 는 Globally Unique Identifier 의 약자입니다.

특정한 난수를 사용하며 유일성을 보장하기 위해 임의로 생성됩니다.

많은 곳에서 사용되고 있지만 오늘은 Network 통신을 하며 사용되는 사례를 살펴볼까 합니다.

 

GUID 혹은 UUID 는 중복 요청을 방지하는 데 사용될 수 있습니다.

클라이언트가 서버로 어떤 request 를 보낼 때 혹은 서버에서 서버로 보낼 때

고유한 식별자를 사용해서 해당 request가 중복된 요청인지 여부를 판가름 할 수 있습니다.

따라서 같은 요청을 100번 보내도, 서버에서는 1번만 처리하게 되는 것입니다.

 

 

어떨 때 사용할까?

 

앞서 timeout 에 대해 알고 계시면 더욱 읽기 수월합니다.

 

https://liltdevs.tistory.com/173

 

Java 코드로 보는 Connection 관련 timeout 에러 정리

Connection timeout handshake 시도 중 서버로부터 응답이 없어서 연결 시간이 초과됨 서버와의 handshake 과정에서 연결이 성립되지 않은 경우라고 볼 수 있다. @Test void ConnectionTimeOut() throws IOException { Socket

liltdevs.tistory.com

 

이를테면 클라이언트 A 는 서버 B 에게 request를 보냈지만

클라이언트 입장에서 timeout 에러가 발생했다면 request가 보내졌는지,

보내졌다면 server가 요청을 처리했는지에 대한 여부는 알기 어렵습니다.

그렇다면 request 를 다시 보내야 할 것입니다.

 

다만, 그 요청이 결제와 관련된 일이라면 굉장히 복잡해질 수 있습니다.

운이 좋아 서버에서 처리하지 않아 결제가 되지 않았다면 다행이지만

그날 운세가 좋지 않았다면 100만원 결제를 2번 하게 될 수 있는 상황이기 때문입니다.

 

이는 서버 간의 통신에서도 마찬가지 입니다.

A 서버에서 B 서버로 요청을 보내며 read timout 이 발생하여

retry 를 하는 상황에서도 운이 좋지 않으면 같은 요청을 2번 보내게 됩니다.

 

아래에서는 이에 대한 해결책으로 GUID ( UUID ) 에 대한 활용법을 살펴보겠습니다.

 

 

예시

 

예시는 의외로 단순할 수 있습니다.

 

public class MyResource {

  private String guid;
  
  // other fields, constructors, getters, setters

  public String getGuid() {
    return guid;
  }

  public void setGuid(String guid) {
    this.guid = guid;
  }
}

중복에 대한 판별을 하게 될 guid 를 갖고 있는 객체입니다.

필요에 따라서 다른 필드들을 갖게 될 수 있습니다.

 

 

@Service
public class MyService {

  private Set<String> processedGuids = new HashSet<>();

  public synchronized boolean isGuidValid(String guid) {
    if (processedGuids.contains(guid)) {
      return false;
    }
    processedGuids.add(guid);
    return true;
  }

  public void processRequest(String guid) {
    // process request
  }
}

서비스에서는 중복된 request 인지 판별하게 됩니다.

서비스에는 set 을 가지고 있고

만약 중복되지 않은 request 라면 guid를 set에 추가하여 true를,

중복된 request 라면 false를 반환합니다.

 

isGuidValid 는 동시성을 고려하여 synchronized 로 구현되어 있습니다.

(위의 set은 실제로는 다른 제품으로 구현해야 할 것입니다. DB 나 Redis를 고려해볼 수도 있겠습니다.)

 

 

@RestController
@RequestMapping("/api")
public class MyController {

  @Autowired
  private MyService myService;

  @GetMapping("/resource")
  public MyResource getResource() {
    String guid = UUID.randomUUID().toString();
    MyResource resource = new MyResource();
    resource.setGuid(guid);
    return resource;
  }

  @PostMapping("/resource")
  public ResponseEntity<?> createResource(@RequestBody MyResource resource) {
    if (!myService.isGuidValid(resource.getGuid())) {
      return ResponseEntity.status(HttpStatus.CONFLICT).build();
    }
    myService.processRequest(resource.getGuid());
    return ResponseEntity.ok(resource);
  }
}

GET /resource 에서 guid 를 갖게 되는 resource 를 반환 한 뒤에

실제로 POST /resource 에 반환한 resource와 함께 요청이 들어오면 정의했던 메소드들을 토대로 진행합니다.

따라서 guid 를 토대로 중복된 요청이라면 CONFLICT 를 내려주고 중복되지 않은 경우 진행합니다.

 

위에서 본 예시는 서버에서 GUID 를 내려준 형태이지만,

클라이언트에서 GUID를 생성하여 request를 보내는 상황에서도 비슷하게 구현할 수 있겠습니다.

Comments