다른 소프트웨어 시스템이 애플리케이션과 상호작용할 수 있도록 공개된 인터페이스. 이를 통해 서비스 간 데이터 교환과 기능 공유가 가능해짐.
레시픽(Recipick): 식재료 판매 및 레시피 제공 커머스 플랫폼
솔픽(Solpick): 카드 및 포인트 관리 서비스
두 서비스는 완전히 별개의 시스템이지만, 사용자가 레시픽에서 쇼핑할 때 솔픽의 카드와 포인트를 사용할 수 있도록, 솔픽을 사용할 때 유저의 결제정보 및 레시피 등을 볼 수 있도록 Open API를 통해 연동되어 있다.
@Service
public class ApiKeyService {
@Value("${solpick.api.key}")
private String validApiKey;
public boolean validateApiKey(String apiKey) {
return validApiKey.equals(apiKey);
}
}
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;
}
}
}
결제 과정에서는 더 복잡한 검증 로직이 필요하다
@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();
}
}
@Data
public class PointRequestDTO {
private int memberId;
private String apiKey;
}
@Data
@Builder
public class PointResponseDTO {
private int points;
private boolean success;
private String message;
}
모든 응답에 성공/실패 여부와 메시지를 포함하여 통일된 구조를 제공
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();
}
예외 발생시 클라이언트가 처리할 수 있는 형태로 표준화된 오류 응답을 제공
결제나 포인트 사용 같은 중요 트랜잭션에서는 같은 요청이 중복 실행되어도 시스템 상태가 동일하게 유지되는 멱등성이 중요하다. 레시픽-솔픽 시스템에서는 주문 ID를 통해 중복 처리를 방지
(잘 구현한건지는 모르겠음)
// API 키 유효성 검사
if (!apiKeyService.validateApiKey(request.getApiKey())) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
모든 API 요청에 API 키 검증을 수행하여 무단 접근을 방지
솔픽과 레시픽이 하나의 애플리케이션이라면 마이크로서비스 아키텍처 형태로 구현될 수 있었을것이다.
마이크로서비스 아키텍처의 핵심 -> 하나의 애플리케이션을 여러 작은 서비스로 분해하는 것
하지만 현재 솔픽과 레시픽은 처음부터 별개의 회사나 시스템 목적으로 설계되었기 때문에 이미 분리된 독립적인 애플리케이션이다. 각 시스템은 완전히 독립적인 비즈니스 도메인을 가지고 있어서, 마이크로서비스의 내부 통신이라기보다는 기업 간 시스템 통합에 가깝다. -> B2B