배시은님 포트폴리오 최종 피드백
메가스터디IT아카데미 SpringBoot 백엔드 개발자 과정 주말반 (2025.02.22 ~ 2025.09.13). 배시은님의 포트폴리오 최종 피드백
배시은님 포트폴리오 최종 피드백
작성일: 2025-09-13
그동안의 노력이 담긴 프로젝트 코드를 바탕으로 마지막 피드백을 정리했습니다. 단순히 정답을 제시하기보다는, 더 나은 개발자로 성장하는 데 도움이 될 만한 깊이 있는 제안과 전문가의 관점을 담으려 노력했습니다. 이 피드백이 앞으로의 개발 여정에 좋은 밑거름이 되기를 바랍니다.
총평 (Overall Review)
프로젝트 전반에 걸쳐 Spring Boot와 웹 개발의 기본기를 탄탄하게 다지려는 노력이 엿보입니다. 특히 이번 주에 완성된 데이터베이스 스키마는 매우 논리적이고 완성도 높게 설계되었습니다. 이는 데이터 모델링의 중요성을 깊이 이해하고 있음을 보여주는 가장 큰 장점입니다.
또한, 에러 로그를 분석하여 문제를 해결하고, build.gradle
의 의존성을 정리하는 등, 단순히 기능을 구현하는 것을 넘어 프로젝트의 건강성을 유지하려는 좋은 습관을 가지고 있습니다.
이제는 개별 기능 구현을 넘어, 소프트웨어 아키처(Software Architecture)의 큰 그림을 그리는 단계로 나아갈 때입니다. 아래 피드백에서는 유지보수성, 확장성, 보안성을 높이는 데 초점을 맞춘 아키텍처 설계와 전문가 수준의 코드 작성을 위한 제안들을 담았습니다.
핵심 피드백 (Key Feedback Points)
1. 데이터베이스 설계
현재 설계된 스키마는 단순히 기능 구현을 넘어, 데이터의 관계, 제약, 성능, 그리고 비즈니스 규칙까지 깊이 있게 고민한 결과물이라는 점에서 매우 인상적입니다.
[강점 상세 분석]
- 정교한 정규화와 데이터 관계 모델링:
- Master-Detail 구조:
flavour
(맛),category
(종류) 같은 핵심 마스터 데이터를 정의하고, 이를 조합한product_variant
(제품)를 파생시키는 구조는 매우 안정적입니다. - 다대다(N:M) 관계의 정석적인 구현: 제품의 수많은 특성들(인증, 원료, 태그)을
variant_cert
,variant_sourcing
,flavour_tag
와 같은 매핑 테이블(혹은 연결 테이블)로 분리한 것은 정규화의 좋은 예시입니다. 각 매핑 테이블은 복합 기본키(PRIMARY KEY (variant_id, feature_id)
)를 사용하여 관계의 유일성을 보장하고 있어 군더더기 없이 깔끔합니다.
- Master-Detail 구조:
- 일관성 있는 명명 규칙 (Naming Convention):
id
(PK),*_id
(FK),is_*
(boolean),*_ko
(i18n),created_at
/updated_at
(timestamps) 등 프로젝트 전반에 걸쳐 일관된 명명 규칙을 적용하여 스키마의 가독성과 예측 가능성을 크게 높였습니다. 이는 협업 시 매우 중요한 역량입니다.
- 데이터 무결성을 위한 다층적 방어:
- 제약조건(Constraints):
UNIQUE
키로 비즈니스적 유일성(e.g.,flavour.slug
)을,FOREIGN KEY
로 테이블 간의 참조 무결성을,CHECK
제약으로 특정 컬럼의 값 범위(variant_reco.slot
)를 강제한 것은 데이터의 신뢰도를 높이는 필수적인 장치들입니다. - 트리거(Triggers):
variant_media
테이블의 트리거는 이 스키마의 백미(白眉)입니다. 이미지의 확장자나 경로 규칙 같은 복잡한 비즈니스 로직을 DB 레벨에서 강제함으로써, 애플리케이션의 실수로 잘못된 데이터가 삽입되는 것을 원천적으로 차단합니다. 이는 ‘방어적 프로그래밍’의 좋은 예시입니다. - 생성 컬럼(Generated Columns):
variant_media
테이블에서 URL로부터 파일명(file_basename
)과 정렬 순서(gallery_num_prefix
)를 자동으로 생성하는 것은 쿼리를 단순화하고 애플리케이션의 부담을 줄여주는 영리한 설계입니다.
- 제약조건(Constraints):
- 효율적인 초기 데이터(Seed) 스크립트:
INSERT ... ON DUPLICATE KEY UPDATE
구문을 사용하여 스크립트를 여러 번 실행해도 데이터가 중복 생성되지 않도록(Idempotent) 작성한 점,UNION ALL
을 활용하여 여러SELECT
결과를 한번에INSERT
하는 등 효율적인 스크립트 작성법을 잘 활용하고 있습니다.
[성장을 위한 심화 제언]
- 데이터 중복과 정규화의 재고찰:
- 관련 필드:
product_variant.variant_description_ko
- 심층 분석: 현재
variant_description_ko
필드는 ‘제품별 설명’이지만, 실제 데이터는 ‘맛(flavour)’에 대한 설명이 대부분 중복되어 저장되고 있습니다. 이는 정규화 원칙에 따르면 개선의 여지가 있습니다. 데이터베이스 설계에서 “데이터의 주인은 누구인가?”를 항상 고민하는 것이 중요합니다. 이 설명의 주인은flavour
일 가능성이 높습니다. - 구체적 제안:
flavour
테이블에base_description_ko
같은 기본 설명 필드를 추가합니다.product_variant
테이블의variant_description_ko
는NULL
을 허용하도록 변경하고, 파인트, 미니컵 등 제품별로 특별히 추가할 설명이 있을 때만 이 필드를 사용합니다. 애플리케이션에서는COALESCE(pv.variant_description_ko, f.base_description_ko)
와 같은 형태로 두 설명을 조합하여 사용하면, 데이터의 일관성은 유지하면서 중복은 제거하고 확장성은 높일 수 있습니다.
- 관련 필드:
- ENUM 타입 사용에 대한 고찰:
- 현황:
variant_media.role
에ENUM
타입을 사용했습니다. - 장단점:
ENUM
은 허용된 값만 저장하도록 강제하고 저장 공간이 작다는 장점이 있습니다. 하지만, 새로운role
(ex: ‘THUMBNAIL’)을 추가하려면ALTER TABLE
을 실행해야 하므로 유연성이 떨어집니다. - 대안:
role
을VARCHAR
타입으로 하고, 별도의media_role_type
같은 마스터 테이블을 만들어FOREIGN KEY
로 연결하는 방법도 있습니다. 이 방식은 확장성은 높지만 구조가 복잡해집니다. 현재 프로젝트 규모에서는ENUM
이 합리적인 선택일 수 있으나, 이러한 설계적 트레이드오프(Trade-off)가 있다는 점을 인지하고 상황에 맞게 선택하는 능력이 중요합니다.
- 현황:
2. 애플리케이션 아키텍처: 서비스 계층(Service Layer)의 도입
가장 중요한 제안입니다. 현재 코드는 컨트롤러(Controller)가 데이터베이스 로직(Repository/Mapper)을 직접 호출하는 2-Tier 구조로 보입니다. 이는 소규모 프로젝트에서는 괜찮지만, 확장성을 고려할 때 서비스 계층(Service Layer)을 도입하여 3-Tier 아키텍처로 전환하는 것을 강력히 권장합니다.
- 구조:
Controller
->Service
->Repository
- Service Layer의 역할:
- 비즈니스 로직의 중앙화: 컨트롤러는 HTTP 요청/응답 처리만 담당하고, 실제 비즈니스 로직(ex: ‘아이스크림 맛을 등록할 때, 관련 태그도 함께 저장한다’)은 서비스 계층에서 처리합니다. 이를 통해 코드가 명확해지고 역할 분리가 확실해집니다.
- 트랜잭션(Transaction) 관리: 여러 데이터베이스 작업을 하나의 단위로 묶어 처리해야 할 때(
@Transactional
어노테이션), 서비스 계층은 트랜잭션의 경계를 설정하는 가장 이상적인 장소입니다. - 재사용성: 하나의 서비스 메소드를 여러 컨트롤러(ex: 웹 컨트롤러, API 컨트롤러)에서 재사용할 수 있습니다.
- 적용 예시:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
// FlavourController.java @Controller public class FlavourController { @Autowired private FlavourService flavourService; // Service 주입 @GetMapping("/flavours/{slug}") public String productDetail(@PathVariable String slug, Model model) { FlavourDetailDTO flavourDetail = flavourService.getFlavourDetailBySlug(slug); model.addAttribute("flavour", flavourDetail); return "flavours/detail"; } } // FlavourService.java @Service public class FlavourService { @Autowired private FlavourMapper flavourMapper; // Mapper(Repository) 주입 @Autowired private TagMapper tagMapper; @Transactional(readOnly = true) public FlavourDetailDTO getFlavourDetailBySlug(String slug) { // 여러 Mapper를 조합하여 비즈니스 로직 수행 Flavour flavour = flavourMapper.findBySlug(slug); List<Tag> tags = tagMapper.findByFlavourId(flavour.getId()); // DTO로 가공하여 Controller에 반환 return new FlavourDetailDTO(flavour, tags); } }
3. API 및 컨트롤러 설계: 유연성과 확장성
- 관련 파일:
ArticleController.java
- 현황:
free-cone-day-flavor
,free-cone-day-history
등 각 게시글에 대한 URL이 메소드 단위로 하드코딩되어 있습니다.1 2
@GetMapping("/news/free-cone-day-flavor") public String freeConeDayFlavor() { ... }
- 제안: 경로 변수(Path Variable)를 사용하여 어떤 게시글이든 하나의 메소드에서 처리할 수 있도록 리팩토링하는 것이 좋습니다. 이는 코드 중복을 없애고 새로운 게시글이 추가될 때마다 코드를 수정할 필요가 없게 만듭니다.
1 2 3 4 5 6 7 8 9 10 11 12
// ArticleController.java @GetMapping("/news/{slug}") public String articleDetail(@PathVariable String slug, Model model) { // slug를 이용해 DB에서 게시글 정보를 조회 (by ArticleService) Article article = articleService.getArticleBySlug(slug); if (article == null) { // 게시글이 없을 경우 404 페이지 등으로 처리 throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Article not found"); } model.addAttribute("article", article); return "news/detail"; // 하나의 템플릿으로 모든 게시글을 렌더링 }
4. 보안: 설정 정보의 안전한 관리
- 관련 파일:
src/main/resources/application.properties
- 피드백: 데이터베이스 계정 정보가 Git에 노출되는 문제는 매우 중요합니다. 기존 피드백을 더 구체화하여, 현업에서 사용하는 설정 외부화(Externalized Configuration) 개념을 적용해 보세요.
- 개선 방안:
- 기본 설정 (
application.properties
): 공통 설정만 남깁니다. - 개발용 설정 (
application-dev.properties
): 내 PC의 DB 접속 정보 등을 넣습니다. - 운영용 설정 (
application-prod.properties
): 실제 서버의 DB 접속 정보 등을 넣습니다. .gitignore
:application-*.properties
를 추가하여 민감한 정보가 Git에 올라가지 않도록 합니다.- 실행: Spring Boot 앱을 실행할 때
spring.profiles.active=dev
또는spring.profiles.active=prod
와 같이 활성 프로파일을 지정하여 설정을 동적으로 변경할 수 있습니다. 이는 배포 환경에 따라 설정을 유연하게 관리하는 핵심 기술입니다.
- 기본 설정 (
마지막으로, 이 모든 것은 배시은님이 지금까지 쌓아온 노력 위에서 가능한 제안들입니다. 탄탄한 기본기 위에 더 견고한 집을 짓는 과정이라 생각하시고, 즐겁게 다음 단계를 고민해 보시길 바랍니다. 그동안 정말 수고 많으셨고, 앞으로 훌륭한 개발자로 성장해 나갈 모습을 기대하고 응원하겠습니다!