AWS RDS 자원 관리 기능 구현
개요
AWS RDS(Relational Database Service)를 헥사고날 아키텍처로 구현합니다.
도메인 추상화명은 RDBMS(Relational Database Management System)를 사용합니다.
추상화명
- RDBMS
- 관계형 데이터베이스 관리 시스템의 일반적인 용어
- AWS RDS, Azure Database, GCP Cloud SQL 모두를 포괄하는 추상화명
- 다른 CSP에서도 자연스럽게 매핑 가능
아키텍처 구조
헥사고날 아키텍처를 준수하여 다음과 같이 구성합니다:
domain/cloud/
├── adapter/outbound/aws/rds/ # AWS RDS 전용 어댑터
│ ├── AwsRdsManagementAdapter.java # RDS 생명주기 관리
│ ├── AwsRdsDiscoveryAdapter.java # RDS 조회
│ ├── AwsRdsMapper.java # AWS SDK ↔ CloudResource 변환
│ └── ...
├── adapter/outbound/aws/config/
│ └── AwsRdsConfig.java # RDS 클라이언트 설정
├── controller/
│ └── RdbmsController.java # REST API (CSP 독립적)
├── dto/
│ ├── RdbmsCreateRequest.java
│ ├── RdbmsUpdateRequest.java
│ ├── RdbmsDeleteRequest.java
│ ├── RdbmsQueryRequest.java
│ └── RdbmsResponse.java
├── port/model/rdbms/ # Command 객체
│ ├── RdbmsCreateCommand.java
│ ├── RdbmsUpdateCommand.java
│ ├── RdbmsDeleteCommand.java
│ ├── RdbmsQuery.java
│ └── GetRdbmsCommand.java
├── port/outbound/rdbms/ # 포트 인터페이스
│ ├── RdbmsManagementPort.java # CRUD 작업
│ ├── RdbmsDiscoveryPort.java # 조회 작업
│ └── RdbmsLifecyclePort.java # 생명주기 관리 (시작/중지/재시작)
├── service/rdbms/
│ ├── RdbmsUseCaseService.java # 유스케이스 서비스 (CSP 독립적)
│ └── RdbmsPortRouter.java # 포트 라우터
└── repository/
└── CloudResourceRepository.java # 기존 리포지토리 활용
구현 단계
Phase 1: 포트 및 모델 정의
1.1 Command 모델 정의 (port/model/rdbms/)
RdbmsCreateCommand.java
/**
* RDBMS 생성 도메인 커맨드 (CSP 중립적)
*
* UseCase Service에서 Adapter로 전달되는 내부 명령 모델입니다.
* CSP 중립적인 필드를 사용하며, 각 CSP Adapter의 Mapper에서 CSP 특화 요청으로 변환합니다.
*
* 필드 매핑 예시:
* - instanceName: AWS(dbInstanceIdentifier), Azure(serverName), GCP(instanceId)
* - instanceSize: AWS(db.t3.micro), Azure(GP_Gen5_2), GCP(db-custom-2-7680)
* - networkSecurityId: AWS(securityGroupId), Azure(firewallRule), GCP(authorizedNetworks)
* - zone: AWS(availabilityZone), Azure(zone), GCP(zone)
*/
@Builder
public record RdbmsCreateCommand(
ProviderType providerType,
String accountScope,
String region,
String serviceKey, // "RDS", "AZURE_DATABASE", "CLOUD_SQL"
String resourceType, // "DATABASE"
String instanceName, // CSP 중립적: AWS(dbInstanceIdentifier), Azure(serverName), GCP(instanceId)
String engine, // "mysql", "postgresql", "mariadb", "oracle", "sqlserver"
String engineVersion, // CSP별 버전 형식 다를 수 있음
String instanceSize, // CSP 중립적: AWS(db.t3.micro), Azure(GP_Gen5_2), GCP(db-custom-2-7680)
Integer allocatedStorage, // GB (모든 CSP 공통)
String masterUsername, // 관리자 사용자명
String masterPassword, // 암호화 필요
String dbName, // 초기 데이터베이스 이름
String networkSecurityId, // CSP 중립적: AWS(securityGroupId), Azure(firewallRule), GCP(authorizedNetworks)
String subnetId, // 서브넷 식별자 (선택적, 일부 CSP만 사용)
Integer port, // 데이터베이스 포트 (기본값: engine별로 다름)
String zone, // CSP 중립적: AWS(availabilityZone), Azure(zone), GCP(zone)
Boolean highAvailability, // 고가용성 설정: AWS(multiAz), Azure(highAvailability), GCP(highAvailability)
Boolean publiclyAccessible, // 공개 접근 허용 여부
Map<String, String> tags,
String tenantKey,
Map<String, Object> providerSpecificConfig, // CSP별 특화 설정
CloudSessionCredential session
) {}
RdbmsUpdateCommand.java
/**
* RDBMS 수정 도메인 커맨드 (CSP 중립적)
*/
@Builder
public record RdbmsUpdateCommand(
ProviderType providerType,
String accountScope,
String region,
String providerResourceId, // RDBMS 인스턴스 ID (CSP별 형식 다를 수 있음)
String instanceSize, // CSP 중립적: 인스턴스 크기 변경
Integer allocatedStorage, // 스토리지 크기 변경 (GB)
String masterPassword, // 선택적: 마스터 패스워드 변경
Boolean applyImmediately, // 즉시 적용 여부 (일부 CSP만 지원)
Map<String, String> tags,
String tenantKey,
Map<String, Object> providerSpecificConfig, // CSP별 특화 설정
CloudSessionCredential session
) {}
RdbmsDeleteCommand.java
/**
* RDBMS 삭제 도메인 커맨드 (CSP 중립적)
*/
@Builder
public record RdbmsDeleteCommand(
ProviderType providerType,
String accountScope,
String region,
String providerResourceId,
Boolean skipSnapshot, // 최종 스냅샷 건너뛰기 (AWS 특화, 다른 CSP는 providerSpecificConfig로)
String snapshotName, // 최종 스냅샷 이름 (선택적)
Boolean deleteAutomatedBackups, // 자동 백업 삭제 여부
String tenantKey,
Map<String, Object> providerSpecificConfig, // CSP별 특화 삭제 옵션
CloudSessionCredential session
) {}
RdbmsQuery.java
/**
* RDBMS 조회 쿼리 (CSP 중립적)
*/
@Builder
public record RdbmsQuery(
ProviderType providerType,
String accountScope,
Set<String> regions,
String instanceName, // CSP 중립적: 인스턴스 이름으로 필터링
String engine, // 엔진 타입으로 필터링
String instanceSize, // 인스턴스 크기로 필터링
String status, // 상태로 필터링 (CSP별 상태 값 다를 수 있음)
Map<String, String> tagsEquals, // 태그로 필터링
int page,
int size
) {}
GetRdbmsCommand.java
@Builder
public record GetRdbmsCommand(
ProviderType providerType,
String accountScope,
String region,
String providerResourceId,
String serviceKey,
String resourceType,
CloudSessionCredential session
) {}
1.2 포트 인터페이스 정의 (port/outbound/rdbms/)
RdbmsManagementPort.java
public interface RdbmsManagementPort {
/**
* RDBMS 인스턴스 생성
*/
CloudResource createRdbms(RdbmsCreateCommand command);
/**
* RDBMS 인스턴스 수정
*/
CloudResource updateRdbms(RdbmsUpdateCommand command);
/**
* RDBMS 인스턴스 삭제
*/
void deleteRdbms(RdbmsDeleteCommand command);
}
RdbmsDiscoveryPort.java
public interface RdbmsDiscoveryPort {
/**
* RDBMS 인스턴스 목록 조회
*/
Page<CloudResource> listRdbmsInstances(RdbmsQuery query, CloudSessionCredential session);
/**
* 특정 RDBMS 인스턴스 조회
*/
Optional<CloudResource> getRdbmsInstance(String instanceId, CloudSessionCredential session);
/**
* RDBMS 인스턴스 상태 조회
*/
String getInstanceStatus(String instanceId, CloudSessionCredential session);
}
RdbmsLifecyclePort.java
public interface RdbmsLifecyclePort {
/**
* RDBMS 인스턴스 시작
*/
void startInstance(String instanceId, CloudSessionCredential session);
/**
* RDBMS 인스턴스 중지
*/
void stopInstance(String instanceId, CloudSessionCredential session);
/**
* RDBMS 인스턴스 재시작
*/
void rebootInstance(String instanceId, CloudSessionCredential session);
}
Phase 2: AWS 어댑터 구현
2.1 Config 클래스 (adapter/outbound/aws/config/AwsRdsConfig.java)
@Slf4j
@Configuration
@ConditionalOnProperty(name = "aws.enabled", havingValue = "true", matchIfMissing = true)
public class AwsRdsConfig {
@Value("${aws.rds.region:us-east-1}")
private String region;
@Value("${aws.rds.endpoint-override:}")
private String endpointOverride;
@Bean
public RdsClient rdsClient() {
log.info("[AwsRdsConfig] Creating RDS client for region: {}", region);
var builder = RdsClient.builder()
.region(Region.of(region))
.credentialsProvider(createThreadLocalCredentialsProvider());
if (!endpointOverride.isEmpty()) {
builder.endpointOverride(URI.create(endpointOverride));
}
return builder.build();
}
// ThreadLocal 자격증명 제공자 구현 (AwsS3Config 참고)
private AwsCredentialsProvider createThreadLocalCredentialsProvider() {
// AwsS3Config의 구현 참고
}
}
2.2 Mapper 클래스 (adapter/outbound/aws/rds/AwsRdsMapper.java)
@Component
public class AwsRdsMapper {
/**
* AWS DBInstance → CloudResource 변환
*/
public CloudResource toCloudResource(DBInstance dbInstance, CloudProvider provider, CloudRegion region, CloudService service) {
return CloudResource.builder()
.resourceId(dbInstance.dbInstanceIdentifier())
.resourceName(dbInstance.dbInstanceIdentifier())
.displayName(dbInstance.dbInstanceIdentifier())
.provider(provider)
.region(region)
.service(service)
.resourceType(CloudResource.ResourceType.DATABASE)
.lifecycleState(mapLifecycleState(dbInstance.dbInstanceStatus()))
.instanceType(dbInstance.dbInstanceClass())
.storageGb((long) dbInstance.allocatedStorage())
.ipAddress(dbInstance.endpoint() != null ? dbInstance.endpoint().address() : null)
.tags(convertTagsToJson(dbInstance.tagList()))
.configuration(convertConfigurationToJson(dbInstance))
.status(mapStatus(dbInstance.dbInstanceStatus()))
.createdInCloud(dbInstance.instanceCreateTime())
.lastSync(LocalDateTime.now())
.build();
}
/**
* RdbmsCreateCommand → CreateDBInstanceRequest 변환
* CSP 중립적 Command를 AWS SDK 요청으로 변환
*/
public CreateDBInstanceRequest toCreateRequest(RdbmsCreateCommand command) {
return CreateDBInstanceRequest.builder()
.dbInstanceIdentifier(command.instanceName()) // instanceName → dbInstanceIdentifier
.engine(command.engine())
.engineVersion(command.engineVersion())
.dbInstanceClass(command.instanceSize()) // instanceSize → dbInstanceClass
.allocatedStorage(command.allocatedStorage())
.masterUsername(command.masterUsername())
.masterUserPassword(command.masterPassword())
.dbName(command.dbName())
.vpcSecurityGroupIds(command.networkSecurityId() != null ?
List.of(command.networkSecurityId()) : null) // networkSecurityId → vpcSecurityGroupIds
.dbSubnetGroupName(getSubnetGroupName(command)) // providerSpecificConfig에서 추출
.port(command.port())
.availabilityZone(command.zone()) // zone → availabilityZone
.multiAz(command.highAvailability() != null && command.highAvailability()) // highAvailability → multiAz
.publiclyAccessible(command.publiclyAccessible())
.tags(convertTags(command.tags()))
.build();
}
/**
* providerSpecificConfig에서 AWS 특화 설정 추출
*/
private String getSubnetGroupName(RdbmsCreateCommand command) {
if (command.providerSpecificConfig() != null) {
return (String) command.providerSpecificConfig().get("subnetGroupName");
}
return null;
}
// 기타 변환 메서드들...
}
2.3 Management Adapter (adapter/outbound/aws/rds/AwsRdsManagementAdapter.java)
@Component
@RequiredArgsConstructor
public class AwsRdsManagementAdapter implements RdbmsManagementPort, ProviderScoped {
private final RdsClient rdsClient;
private final AwsRdsMapper mapper;
private final CloudErrorTranslator errorTranslator;
@Override
public CloudResource createRdbms(RdbmsCreateCommand command) {
try {
CreateDBInstanceRequest request = mapper.toCreateRequest(command);
CreateDBInstanceResponse response = rdsClient.createDBInstance(request);
// 생성된 인스턴스 조회
DBInstance dbInstance = waitForInstanceAvailable(response.dbInstance().dbInstanceIdentifier());
return mapper.toCloudResource(dbInstance, ...);
} catch (Throwable t) {
throw errorTranslator.translate(t);
}
}
@Override
public ProviderType getProviderType() {
return ProviderType.AWS;
}
}
2.4 Discovery Adapter (adapter/outbound/aws/rds/AwsRdsDiscoveryAdapter.java)
@Component
@RequiredArgsConstructor
public class AwsRdsDiscoveryAdapter implements RdbmsDiscoveryPort, ProviderScoped {
private final RdsClient rdsClient;
private final AwsRdsMapper mapper;
@Override
public Page<CloudResource> listRdbmsInstances(RdbmsQuery query, CloudSessionCredential session) {
// AWS SDK DescribeDBInstances 호출
// 페이징 처리
// CloudResource로 변환
}
@Override
public ProviderType getProviderType() {
return ProviderType.AWS;
}
}
2.5 Lifecycle Adapter (adapter/outbound/aws/rds/AwsRdsLifecycleAdapter.java)
@Component
@RequiredArgsConstructor
public class AwsRdsLifecycleAdapter implements RdbmsLifecyclePort, ProviderScoped {
private final RdsClient rdsClient;
@Override
public void startInstance(String instanceId, CloudSessionCredential session) {
// AWS SDK StartDBInstance 호출
}
@Override
public ProviderType getProviderType() {
return ProviderType.AWS;
}
}
Phase 3: Service 계층 구현
3.1 Port Router (service/rdbms/RdbmsPortRouter.java)
@Component
@RequiredArgsConstructor
public class RdbmsPortRouter {
private final Map<ProviderType, RdbmsManagementPort> managementMap;
private final Map<ProviderType, RdbmsDiscoveryPort> discoveryMap;
private final Map<ProviderType, RdbmsLifecyclePort> lifecycleMap;
public RdbmsManagementPort management(ProviderType type) {
return require(managementMap, type);
}
public RdbmsDiscoveryPort discovery(ProviderType type) {
return require(discoveryMap, type);
}
public RdbmsLifecyclePort lifecycle(ProviderType type) {
return require(lifecycleMap, type);
}
}
3.2 UseCase Service (service/rdbms/RdbmsUseCaseService.java)
@Slf4j
@Service
@RequiredArgsConstructor
public class RdbmsUseCaseService {
private static final String SERVICE_KEY = "RDS";
private static final String RESOURCE_TYPE = "DATABASE";
private final RdbmsPortRouter portRouter;
private final CapabilityGuard capabilityGuard;
private final AccountCredentialManagementPort credentialPort;
private final CloudResourceRepository resourceRepository;
/**
* RDBMS 인스턴스 생성
*/
@Transactional
public CloudResource createRdbms(RdbmsCreateRequest request) {
ProviderType providerType = request.getProviderType();
String accountScope = request.getAccountScope();
// Capability 검증
capabilityGuard.ensureSupported(providerType, SERVICE_KEY, RESOURCE_TYPE, Operation.CREATE);
// 세션 획득 (JIT 패턴)
CloudSessionCredential session = getSession(providerType, accountScope);
// Command 변환
RdbmsCreateCommand command = toCreateCommand(request, session);
// 어댑터 호출
CloudResource resource = portRouter.management(providerType).createRdbms(command);
// 리소스 저장
CloudResource saved = resourceRepository.save(resource);
return saved;
}
/**
* RDBMS 인스턴스 목록 조회
*/
@Transactional(readOnly = true)
public Page<CloudResource> listRdbmsInstances(ProviderType providerType, String accountScope, RdbmsQueryRequest request) {
CloudSessionCredential session = getSession(providerType, accountScope);
RdbmsQuery query = toQuery(request);
return portRouter.discovery(providerType).listRdbmsInstances(query, session);
}
// 기타 메서드들...
private CloudSessionCredential getSession(ProviderType providerType, String accountScope) {
String tenantKey = TenantContextHolder.getCurrentTenantKeyOrThrow();
return credentialPort.getSession(tenantKey, accountScope, providerType);
}
}
Phase 4: Controller 및 DTO 구현
4.1 DTO 클래스 (dto/)
RdbmsCreateRequest.java
/**
* RDBMS 생성 요청 DTO (CSP 중립적)
*
* Controller에서 받는 요청 객체로, CSP 중립적인 필드만 포함합니다.
* CSP 특화 설정은 providerSpecificConfig에 포함됩니다.
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RdbmsCreateRequest {
@NotNull
private ProviderType providerType;
@NotBlank
private String accountScope;
@NotBlank
private String region;
@NotBlank
@Size(min = 1, max = 100)
private String instanceName; // CSP 중립적
@NotBlank
private String engine; // mysql, postgresql, mariadb, oracle, sqlserver
private String engineVersion;
@NotBlank
private String instanceSize; // CSP 중립적: AWS(db.t3.micro), Azure(GP_Gen5_2), GCP(db-custom-2-7680)
@Min(20)
private Integer allocatedStorage; // GB
@NotBlank
private String masterUsername;
@NotBlank
@Size(min = 8)
private String masterPassword;
private String dbName;
private String networkSecurityId; // CSP 중립적: AWS(securityGroupId), Azure(firewallRule), GCP(authorizedNetworks)
private String subnetId; // 선택적
private Integer port; // 기본값: engine별로 다름
private String zone; // CSP 중립적: 가용 영역
private Boolean highAvailability = false; // CSP 중립적: 고가용성 설정
private Boolean publiclyAccessible = false;
private Map<String, String> tags;
/**
* CSP별 특화 설정
*
* AWS 예시:
* - subnetGroupName: "my-db-subnet-group"
* - parameterGroupName: "default.mysql8.0"
*
* Azure 예시:
* - resourceGroupName: "my-resource-group"
* - sku: { "name": "GP_Gen5_2", "tier": "GeneralPurpose", "capacity": 2 }
*
* GCP 예시:
* - databaseFlags: [{"name": "max_connections", "value": "100"}]
* - backupConfiguration: {"enabled": true, "startTime": "23:00"}
*/
private Map<String, Object> providerSpecificConfig;
}
RdbmsUpdateRequest.java
/**
* RDBMS DTO (CSP 중립적)
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RdbmsUpdateRequest {
@NotNull
private ProviderType providerType;
@NotBlank
private String accountScope;
@NotBlank
private String instanceId; // 수정할 인스턴스 ID
private String instanceSize; // CSP 중립적: 인스턴스 크기 변경
@Min(20)
private Integer allocatedStorage; // 스토리지 크기 변경 (GB)
@Size(min = 8)
private String masterPassword; // 선택적: 마스터 패스워드 변경
private Boolean applyImmediately; // 즉시 적용 여부 (일부 CSP만 지원)
private Map<String, String> tagsToAdd; // 추가할 태그
private Map<String, String> tagsToRemove; // 제거할 태그
/**
* CSP별 특화 설정
*/
private Map<String, Object> providerSpecificConfig;
}
RdbmsDeleteRequest.java
/**
* RDBMS 삭제 요청 DTO (CSP 중립적)
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RdbmsDeleteRequest {
@NotNull
private ProviderType providerType;
@NotBlank
private String accountScope;
@NotBlank
private String instanceId; // 삭제할 인스턴스 ID
@Builder.Default
private Boolean skipSnapshot = false; // 최종 스냅샷 건너뛰기
private String snapshotName; // 최종 스냅샷 이름 (선택적)
@Builder.Default
private Boolean deleteAutomatedBackups = false; // 자동 백업 삭제 여부
private String reason; // 삭제 이유 (감사 로그용)
/**
* CSP별 특화 삭제 옵션
*/
private Map<String, Object> providerSpecificConfig;
/**
* 기본 삭제 요청 생성
*/
public static RdbmsDeleteRequest basic(String instanceId) {
return RdbmsDeleteRequest.builder()
.instanceId(instanceId)
.skipSnapshot(false)
.deleteAutomatedBackups(false)
.build();
}
}
RdbmsQueryRequest.java
/**
* RDBMS 조회 요청 DTO (CSP 중립적)
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RdbmsQueryRequest {
@NotNull
private ProviderType providerType;
@NotBlank
private String accountScope;
private Set<String> regions; // 조회할 리전 목록
private String instanceName; // 인스턴스 이름으로 필터링
private String engine; // 엔진 타입으로 필터링
private String instanceSize; // 인스턴스 크기로 필터링
private String status; // 상태로 필터링
private Map<String, String> tags; // 태그로 필터링
@Min(0)
@Builder.Default
private int page = 0;
@Min(1)
@Max(100)
@Builder.Default
private int size = 20;
}
RdbmsResponse.java
/**
* RDBMS 응답 DTO (CSP 중립적)
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RdbmsResponse {
private Long id;
private String resourceId;
private String resourceName;
private ProviderType providerType;
private String region;
private String engine;
private String engineVersion;
private String instanceSize; // CSP 중립적: AWS(db.t3.micro), Azure(GP_Gen5_2), GCP(db-custom-2-7680)
private Long storageGb;
private String status;
private String lifecycleState;
private String endpoint; // 데이터베이스 엔드포인트
private Integer port;
private Boolean highAvailability; // 고가용성 설정 여부
private Boolean publiclyAccessible; // 공개 접근 허용 여부
private Map<String, String> tags;
private LocalDateTime createdAt;
private LocalDateTime lastSync;
/**
* CloudResource → RdbmsResponse 변환
*/
public static RdbmsResponse from(CloudResource resource) {
// CloudResource의 configuration JSON에서 추가 정보 추출
// instanceSize는 resource.getInstanceType() 또는 configuration에서 추출
return RdbmsResponse.builder()
.id(resource.getId())
.resourceId(resource.getResourceId())
.resourceName(resource.getResourceName())
.providerType(resource.getProvider().getProviderType())
.region(resource.getRegion() != null ? resource.getRegion().getRegionKey() : null)
.instanceSize(resource.getInstanceType()) // 또는 configuration에서 추출
.storageGb(resource.getStorageGb())
.status(resource.getStatus() != null ? resource.getStatus().name() : null)
.lifecycleState(resource.getLifecycleState() != null ? resource.getLifecycleState().name() : null)
.endpoint(resource.getIpAddress()) // 또는 configuration에서 추출
.tags(parseTags(resource.getTags()))
.createdAt(resource.getCreatedAt())
.lastSync(resource.getLastSync())
.build();
}
private static Map<String, String> parseTags(String tagsJson) {
// JSON 문자열을 Map으로 변환
// 구현 생략
return Map.of();
}
}
4.2 Controller (controller/RdbmsController.java)
@Slf4j
@RestController
@RequestMapping("/api/v1/cloud/providers/{provider}/accounts/{accountScope}/rdbms/instances")
@RequiredArgsConstructor
@Tag(name = "RDBMS Management", description = "RDBMS 인스턴스 관리 API (멀티 클라우드 지원)")
public class RdbmsController {
private final RdbmsUseCaseService rdbmsUseCaseService;
@PostMapping
@Operation(summary = "RDBMS 인스턴스 생성")
public ResponseEntity<ApiResponse<RdbmsResponse>> createRdbms(
@PathVariable ProviderType provider,
@PathVariable String accountScope,
@Valid @RequestBody RdbmsCreateRequest request) {
request.setProviderType(provider);
request.setAccountScope(accountScope);
CloudResource resource = rdbmsUseCaseService.createRdbms(request);
RdbmsResponse response = RdbmsResponse.from(resource);
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.success(response, "RDBMS 인스턴스 생성에 성공했습니다."));
}
@GetMapping
@Operation(summary = "RDBMS 인스턴스 목록 조회")
public ResponseEntity<ApiResponse<Page<RdbmsResponse>>> listRdbmsInstances(
@PathVariable ProviderType provider,
@PathVariable String accountScope,
@Valid RdbmsQueryRequest request) {
// 구현...
}
// 기타 엔드포인트들...
}
Phase 5: Capability 등록
CapabilityRegistry에 RDS 기능 등록
// AwsCapabilityConfig.java에 추가
@Bean
public CspCapability awsRdsCapability() {
return CspCapability.builder()
.providerType(ProviderType.AWS)
.serviceKey("RDS")
.resourceType("DATABASE")
.supportsCreate(true)
.supportsRead(true)
.supportsUpdate(true)
.supportsDelete(true)
.supportsStart(true)
.supportsStop(true)
.supportsReboot(true)
.build();
}
고려사항
1. 헥사고날 아키텍처 준수
- ✅ 포트 인터페이스: CSP 독립적인 비즈니스 계약 정의
- ✅ 어댑터 분리: AWS 전용 로직은
adapter/outbound/aws/rds/에만 존재
- ✅ 의존성 방향: Service → Port → Adapter (단방향)
- ✅ 도메인 모델:
CloudResource 엔티티 활용
2. CSP 독립성 보장
- ✅ Controller: 특정 CSP에 종속되지 않는 공통 API만 제공
- ✅ Service: CSP별 차이는 Port 인터페이스로 추상화
- ✅ Command: CSP 중립적인 필드만 포함,
providerSpecificConfig로 확장 가능
3. 보안 및 자격증명 관리
- ✅ JIT 세션 관리: Service 레벨에서 세션 획득 및 Port에 전달
- ✅ 암호화: 마스터 패스워드는 암호화하여 전달 (Command에서 처리)
- ✅ ThreadLocal 자격증명: 멀티 테넌트 환경 지원
4. 리소스 동기화
- ✅ CloudResource 저장: 생성/수정 시
CloudResourceRepository에 저장
- ✅ 동기화 전략:
- 생성/수정 시 즉시 저장
- 조회 시 최신 상태 반영 (
lastSync 필드 활용)
- 주기적 동기화 작업 고려 (별도 스케줄러)
5. 에러 처리
- ✅ CloudErrorTranslator: AWS SDK 예외를 도메인 예외로 변환
- ✅ BusinessException: 비즈니스 로직 예외 처리
- ✅ 에러 코드:
CloudErrorCode에 RDBMS 관련 에러 코드 추가
6. 트랜잭션 관리
- ✅ @transactional: Service 메서드에 적절한 트랜잭션 설정
- ✅ 읽기 전용: 조회 메서드는
@Transactional(readOnly = true)
7. Capability 검증
- ✅ CapabilityGuard: 작업 전 CSP 지원 여부 검증
- ✅ 동적 검증: 런타임에 Capability 확인
8. 데이터베이스 엔진별 차이
- ✅ 엔진 추상화:
engine 필드로 엔진 타입 지정
- ✅ 엔진별 설정:
providerSpecificConfig로 엔진별 특화 설정 지원
- ✅ 매퍼에서 처리: 각 CSP Adapter의 Mapper에서 엔진별 차이 흡수
8-1. CSP 중립성 보장
9. RDS 특화 기능
- ✅ 스냅샷 관리: 삭제 시 최종 스냅샷 옵션
- ✅ Multi-AZ: 고가용성 설정
- ✅ 자동 백업: 백업 정책 설정
- ✅ 파라미터 그룹: 데이터베이스 파라미터 설정 (향후 확장)
10. 테스트 전략
- ✅ 단위 테스트: 각 Adapter, Service, Controller 테스트
- ✅ 통합 테스트: LocalStack을 활용한 AWS 통합 테스트
- ✅ Mock 테스트: Port 인터페이스 Mock으로 Service 테스트
11. 성능 고려사항
- ✅ 페이징: 대량 조회 시 페이징 처리
- ✅ 비동기 처리: 장시간 작업(생성, 수정)은 비동기 고려
- ✅ 캐싱: 자주 조회되는 인스턴스 정보 캐싱 고려
12. 모니터링 및 로깅
- ✅ 구조화된 로깅: 모든 레이어에서 일관된 로깅
- ✅ 마스킹: 민감 정보(패스워드 등) 마스킹 처리
- ✅ 메트릭: RDS 작업 성공/실패 메트릭 수집
구현 체크리스트
Phase 1: 포트 및 모델
Phase 2: AWS 어댑터
Phase 3: Service 계층
Phase 4: Controller 및 DTO
Phase 5: Capability 및 설정
Phase 6: 테스트
참고 자료
AWS RDS 자원 관리 기능 구현
개요
AWS RDS(Relational Database Service)를 헥사고날 아키텍처로 구현합니다.
도메인 추상화명은 RDBMS(Relational Database Management System)를 사용합니다.
추상화명
아키텍처 구조
헥사고날 아키텍처를 준수하여 다음과 같이 구성합니다:
구현 단계
Phase 1: 포트 및 모델 정의
1.1 Command 모델 정의 (
port/model/rdbms/)RdbmsCreateCommand.java
RdbmsUpdateCommand.java
RdbmsDeleteCommand.java
RdbmsQuery.java
GetRdbmsCommand.java
1.2 포트 인터페이스 정의 (
port/outbound/rdbms/)RdbmsManagementPort.java
RdbmsDiscoveryPort.java
RdbmsLifecyclePort.java
Phase 2: AWS 어댑터 구현
2.1 Config 클래스 (
adapter/outbound/aws/config/AwsRdsConfig.java)2.2 Mapper 클래스 (
adapter/outbound/aws/rds/AwsRdsMapper.java)2.3 Management Adapter (
adapter/outbound/aws/rds/AwsRdsManagementAdapter.java)2.4 Discovery Adapter (
adapter/outbound/aws/rds/AwsRdsDiscoveryAdapter.java)2.5 Lifecycle Adapter (
adapter/outbound/aws/rds/AwsRdsLifecycleAdapter.java)Phase 3: Service 계층 구현
3.1 Port Router (
service/rdbms/RdbmsPortRouter.java)3.2 UseCase Service (
service/rdbms/RdbmsUseCaseService.java)Phase 4: Controller 및 DTO 구현
4.1 DTO 클래스 (
dto/)RdbmsCreateRequest.java
RdbmsUpdateRequest.java
RdbmsDeleteRequest.java
RdbmsQueryRequest.java
RdbmsResponse.java
4.2 Controller (
controller/RdbmsController.java)Phase 5: Capability 등록
CapabilityRegistry에 RDS 기능 등록
고려사항
1. 헥사고날 아키텍처 준수
adapter/outbound/aws/rds/에만 존재CloudResource엔티티 활용2. CSP 독립성 보장
providerSpecificConfig로 확장 가능3. 보안 및 자격증명 관리
4. 리소스 동기화
CloudResourceRepository에 저장lastSync필드 활용)5. 에러 처리
CloudErrorCode에 RDBMS 관련 에러 코드 추가6. 트랜잭션 관리
@Transactional(readOnly = true)7. Capability 검증
8. 데이터베이스 엔진별 차이
engine필드로 엔진 타입 지정providerSpecificConfig로 엔진별 특화 설정 지원8-1. CSP 중립성 보장
✅ 필드명 중립화:
dbInstanceIdentifier→instanceName(모든 CSP 공통)instanceClass→instanceSize(VM 패턴과 일치)vpcSecurityGroupId→networkSecurityId(VM 패턴과 일치)availabilityZone→zone(VM 패턴과 일치)multiAz→highAvailability(모든 CSP 공통 개념)✅ CSP 특화 필드 제거:
subnetGroupName(AWS 전용) →providerSpecificConfig로 이동skipFinalSnapshot(AWS 전용) →skipSnapshot으로 일반화하거나providerSpecificConfig로 이동✅ 확장성:
providerSpecificConfig로 CSP별 특화 설정 지원subnetGroupName,parameterGroupName,optionGroupName등resourceGroupName,sku,storageProfile등databaseFlags,backupConfiguration,ipConfiguration등9. RDS 특화 기능
10. 테스트 전략
11. 성능 고려사항
12. 모니터링 및 로깅
구현 체크리스트
Phase 1: 포트 및 모델
RdbmsCreateCommand정의RdbmsUpdateCommand정의RdbmsDeleteCommand정의RdbmsQuery정의GetRdbmsCommand정의RdbmsManagementPort인터페이스 정의RdbmsDiscoveryPort인터페이스 정의RdbmsLifecyclePort인터페이스 정의Phase 2: AWS 어댑터
AwsRdsConfig클래스 생성AwsRdsMapper클래스 생성AwsRdsManagementAdapter구현AwsRdsDiscoveryAdapter구현AwsRdsLifecycleAdapter구현ProviderScoped인터페이스 구현Phase 3: Service 계층
RdbmsPortRouter생성RdbmsUseCaseService구현Phase 4: Controller 및 DTO
RdbmsCreateRequestDTO 생성RdbmsUpdateRequestDTO 생성RdbmsDeleteRequestDTO 생성RdbmsQueryRequestDTO 생성RdbmsResponseDTO 생성RdbmsController구현Phase 5: Capability 및 설정
Phase 6: 테스트
참고 자료