Open API를 통한 B2B 통합

MELIES·2025년 4월 6일

Open API란?

다른 소프트웨어 시스템이 애플리케이션과 상호작용할 수 있도록 공개된 인터페이스. 이를 통해 서비스 간 데이터 교환과 기능 공유가 가능해짐.

레시픽(Recipick): 식재료 판매 및 레시피 제공 커머스 플랫폼
솔픽(Solpick): 카드 및 포인트 관리 서비스

두 서비스는 완전히 별개의 시스템이지만, 사용자가 레시픽에서 쇼핑할 때 솔픽의 카드와 포인트를 사용할 수 있도록, 솔픽을 사용할 때 유저의 결제정보 및 레시피 등을 볼 수 있도록 Open API를 통해 연동되어 있다.

API 인증 구현 방식

@Service
public class ApiKeyService {
    @Value("${solpick.api.key}")
    private String validApiKey;

    public boolean validateApiKey(String apiKey) {
        return validApiKey.equals(apiKey);
    }
}

API 호출 시마다 보안을 위해 API 키 인증을 한다.

포인트 조회 API 예시

솔픽 API 컨트롤러

@RestController
@RequestMapping("/solpick/api")
public class ExternalPointController {
    @PostMapping("/points")
    public PointResponseDTO getUserPoints(@RequestBody PointRequestDTO request) {
        // API 키 유효성 검사
        if (!apiKeyService.validateApiKey(request.getApiKey())) {
            return PointResponseDTO.builder()
                    .success(false)
                    .message("유효하지 않은 API 키입니다.")
                    .points(0)
                    .build();
        }

        try {
            // 레시픽 회원 ID로 포인트 조회
            int points = pointService.getUserPointsByRecipickUserId(request.getMemberId());
            
            return PointResponseDTO.builder()
                    .success(true)
                    .message("포인트 조회 성공")
                    .points(points)
                    .build();
        } catch (Exception e) {
            return PointResponseDTO.builder()
                    .success(false)
                    .message("포인트 조회 중 오류가 발생했습니다: " + e.getMessage())
                    .points(0)
                    .build();
        }
    }
}

레시픽에서의 API 호출 클라이언트

@Component
public class SolpickPointService {
    @Value("${solpick.api.base-url}")
    private String baseUrl;

    @Value("${solpick.api.key}")
    private String apiKey;

    public int getUserPoints(int memberId) {
        try {
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);

            PointRequestDTO request = new PointRequestDTO();
            request.setMemberId(memberId);
            request.setApiKey(apiKey);

            HttpEntity<PointRequestDTO> requestEntity = new HttpEntity<>(request, headers);
            
            ResponseEntity<PointResponseDTO> response = restTemplate.postForEntity(
                baseUrl + "/solpick/api/points", 
                requestEntity, 
                PointResponseDTO.class
            );

            if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
                return response.getBody().getPoints();
            } else {
                return 0;
            }
        } catch (Exception e) {
            return 0;
        }
    }
}

카드 결제 검증 API

결제 과정에서는 더 복잡한 검증 로직이 필요하다

@Service
public class CardValidationService {
    @Transactional
    public VerifyCardResponseDTO verifyCardAndUsePoints(
            int recipickUserId,
            String cardNumber,
            String cardExpiry) {

        // 회원 정보 조회
        Optional<Member> memberOpt = memberRepository.findByRecipickUserId(recipickUserId);
        if (memberOpt.isEmpty()) {
            return VerifyCardResponseDTO.builder()
                    .success(false)
                    .message("회원 정보를 찾을 수 없습니다.")
                    .isValid(false)
                    .build();
        }

        Member member = memberOpt.get();

        // 카드 정보 검증
        Optional<Card> cardOpt = cardRepository.findByUserIdAndCardNumberAndCardStatus(
                member.getId(), cardNumber, "ACTIVE");

        if (cardOpt.isEmpty()) {
            return VerifyCardResponseDTO.builder()
                    .success(false)
                    .message("등록된 카드 정보를 찾을 수 없습니다.")
                    .isValid(false)
                    .build();
        }

        // 결과 반환
        return VerifyCardResponseDTO.builder()
                .success(true)
                .message("카드 검증이 완료되었습니다.")
                .isValid(true)
                .build();
    }
}

Open API 설계 시 고려사항

1. 명확한 요청/응답 구조

@Data
public class PointRequestDTO {
    private int memberId;
    private String apiKey;
}

@Data
@Builder
public class PointResponseDTO {
    private int points;
    private boolean success;
    private String message;
}

모든 응답에 성공/실패 여부와 메시지를 포함하여 통일된 구조를 제공

2. 오류 처리 표준화

try {
    // API 로직
    return PointResponseDTO.builder()
            .success(true)
            .message("성공")
            .points(points)
            .build();
} catch (Exception e) {
    return PointResponseDTO.builder()
            .success(false)
            .message("오류: " + e.getMessage())
            .points(0)
            .build();
}

예외 발생시 클라이언트가 처리할 수 있는 형태로 표준화된 오류 응답을 제공

3. 멱등성(Idempotency) 보장

결제나 포인트 사용 같은 중요 트랜잭션에서는 같은 요청이 중복 실행되어도 시스템 상태가 동일하게 유지되는 멱등성이 중요하다. 레시픽-솔픽 시스템에서는 주문 ID를 통해 중복 처리를 방지
(잘 구현한건지는 모르겠음)

4. 보안 레이어 구현

// API 키 유효성 검사
if (!apiKeyService.validateApiKey(request.getApiKey())) {
    return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}

모든 API 요청에 API 키 검증을 수행하여 무단 접근을 방지


솔픽과 레시픽이 하나의 애플리케이션이라면?

솔픽과 레시픽이 하나의 애플리케이션이라면 마이크로서비스 아키텍처 형태로 구현될 수 있었을것이다.

마이크로서비스 아키텍처의 핵심 -> 하나의 애플리케이션을 여러 작은 서비스로 분해하는 것

하지만 현재 솔픽과 레시픽은 처음부터 별개의 회사나 시스템 목적으로 설계되었기 때문에 이미 분리된 독립적인 애플리케이션이다. 각 시스템은 완전히 독립적인 비즈니스 도메인을 가지고 있어서, 마이크로서비스의 내부 통신이라기보다는 기업 간 시스템 통합에 가깝다. -> B2B

profile
42 Seoul

0개의 댓글