프로젝트 개요
AgileBoot 백엔드는 Spring Boot 기반의 엔터프라이즈급 애플리케이션 개발을 위한 구조화된 템플릿입니다. 계층형 아키텍처와 도메인 주도 설계 원칙을 적용하여 대규모 팀 협업과 장기적인 유지보수를 고려한 구조로 설계되었습니다.
코드베이스 구성
프로젝트 루트에서 주요 디렉터리는 다음과 같이 배치됩니다:
agileboot-backend/
├── src/main/java/io/agileboot/
│ ├── BootstrapEntry.java # 애플리케이션 진입점
│ ├── shared/ # 공통 유틸리티 및 상수
│ ├── configuration/ # 빈 설정 및 프로퍼티 바인딩
│ ├── interfaces/ # REST API 엔드포인트
│ ├── application/ # 유스케이스 및 서비스 로직
│ ├── domain/ # 핵심 비즈니스 엔티티 및 규칙
│ ├── persistence/ # 데이터 접근 계층
│ └── integration/ # 외부 시스템 연동
├── src/main/resources/
│ ├── application.yaml # 기본 설정
│ ├── application-local.yaml # 로컬 개발 환경
│ └── db/migration/ # Flyway 스크립트
└── infrastructure/
├── docker-compose.yml
└── kubernetes/
애플리케이션 부트스트랩
진입점 클래스는 표준 Spring Boot 구조를 따르되, 추가적인 빈 등록 전략을 포함합니다:
package io.agileboot;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableCaching
@EnableAsync
public class BootstrapEntry {
public static void main(String[] runtimeArgs) {
new SpringApplicationBuilder(BootstrapEntry.class)
.properties("spring.config.name=application,secrets")
.run(runtimeArgs);
}
}
환경별 설정 관리
프로파일 기반 설정 분리를 통해 환경 간 차이를 명확히 구분합니다. application.yaml은 공통 설정만 담당하고, 환경별 파일은 오버라이드할 항목만 정의합니다:
# application.yaml (공통 설정)
spring:
application:
name: agile-platform
jackson:
default-property-inclusion: non_null
management:
endpoints:
web:
exposure:
include: health,metrics,prometheus
---
# application-local.yaml (개발 환경)
spring:
datasource:
url: jdbc:postgresql://localhost:5432/agile_dev
hikari:
maximum-pool-size: 5
logging:
level:
io.agileboot: DEBUG
레이어 간 의존성 방향
프로젝트는 내부 의존성 규칙을 엄격히 적용합니다. 상위 계층만 하위 계층을 참조하며, 도메인 계층은 어떤 기술적 의존성도 갖지 않습니다:
// interfaces 레이어: HTTP 요청 처리
@RestController
@RequestMapping("/api/v1/inventory")
public class StockController {
private final StockManageUseCase stockService;
@PostMapping("/adjustment")
public ResponseEntity<StockResult> modifyQuantity(
@RequestBody @Valid StockAdjustmentRequest payload) {
var command = payload.toCommand();
var outcome = stockService.executeAdjustment(command);
return ResponseEntity.ok(StockResult.from(outcome));
}
}
// application 레이어: 트랜잭션 및 유스케이스 조율
@Service
@Transactional
public class StockManageUseCase {
private final InventoryRepository repository;
private final EventPublisher eventPublisher;
public AdjustmentOutcome executeAdjustment(AdjustStockCommand cmd) {
var item = repository.findBySku(cmd.sku())
.orElseThrow(() -> new StockNotFoundException(cmd.sku()));
item.decrease(cmd.quantity(), cmd.reason());
repository.save(item);
eventPublisher.publish(new StockLevelChanged(item.getSku(), item.getAvailable()));
return AdjustmentOutcome.success(item.getAvailable());
}
}
데이터 영속성 설정
MyBatis Plus와 함께 데이터베이스 중립적인 매퍼 인터페이스를 정의합니다:
@Mapper
public interface ProductCatalogMapper {
@Select("""
SELECT p.id, p.sku_code, p.name, c.category_name
FROM products p
JOIN categories c ON p.category_id = c.id
WHERE p.status = #{status}
AND p.created_at > #{since}
ORDER BY p.priority DESC
""")
List<ProductSummary> selectActiveProducts(
@Param("status") ProductStatus status,
@Param("since") OffsetDateTime since);
@Insert("""
INSERT INTO products (sku_code, name, category_id, unit_price, status)
VALUES (#{sku}, #{name}, #{categoryId}, #{price}, #{status})
""")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertNewProduct(ProductEntity record);
}
컨테이너화 및 배포
멀티스테이지 빌드를 적용한 컨테이너 이미지는 최소 런타임만 포함합니다:
# Dockerfile
FROM eclipse-temurin:21-jdk-alpine AS builder
WORKDIR /workspace
COPY pom.xml .
COPY src ./src
RUN --mount=type=cache,target=/root/.m2 mvn package -DskipTests
FROM eclipse-temurin:21-jre-alpine
RUN addgroup -S runtime && adduser -S runtime -G runtime
USER runtime
COPY --from=builder /workspace/target/*.jar app.jar
ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75.0", "-jar", "/app.jar"]