BEYOND SW 캠프 23기 22주차 회고

이지연·2026년 4월 21일
post-thumbnail

Week 22

기간: 2026.04.13 - 2026.04.17
커리큘럼: 최종 프로젝트 (HR 인사시스템 SaaS 플랫폼) - 평가 관리 도메인 설계 및 구현


1. Facts — 무엇을 했나?

< 평가 관리 모듈 도메인 설계 및 핵심 로직 구현 >

  • evaluation_season → evaluation_group → evaluation_design → evaluation_response → calibration_history 흐름으로 이어지는 평가 전체 라이프사이클을 설계하고 구현했다.
  • EvaluationSeasonDRAFT → ACTIVE → CLOSED 상태로 관리하고, 결과 공개는 resultsPublishedAt 타임스탬프로 별도 제어하는 구조를 만들었다. 시즌 상태와 결과 공개를 분리함으로써 종결 후 결과 공개, 또는 공개 없이 행정 종결하는 시나리오를 모두 지원하도록 설계했다.
  • EvaluationGroup에 평가 대상자 목록과 평가자-대상자 매핑(evaluatorMapsJson)을 설계해, 자기평가(SELF), 하향(DOWNWARD), 상향(UPWARD), 동료(PEER) 등 다면평가 유형을 하나의 그룹 구조로 수용했다.
  • EvaluationDesign에 평가 항목(sectionsJson)과 등급 설정(gradeConfigJson)을 분리 저장해, 평가 양식 자체를 데이터로 관리하고 그룹별로 다른 설계를 할당할 수 있도록 구성했다.
  • EvaluationResponseanswersJson, calibrationJson, normalizedScore를 두고, 평가 응답 저장 → 제출 → 캘리브레이션 등급 조정 → 결과 공개 흐름을 구현했다.
  • 시즌 시작(startSeason()) 시점에 피평가자의 ACTIVE/COMPLETED 목표를 goalSnapshotJson으로 캡처해 각 EvaluationResponse에 저장하는 구조를 만들었다. 나중에 목표가 수정·삭제되더라도 평가 당시 목표 기준을 재현할 수 있도록 설계했다.
  • CalibrationHistory로 등급 조정 이력을 별도 테이블에 추적해, 누가 언제 어떤 이유로 등급을 바꿨는지 감사 가능한 구조를 갖췄다.
  • 결과 공개 후 자동으로 피드백 면담(meeting_record)이 생성되는 흐름을 publishResults()에 연결했다.

2. Feelings — 어떻게 느꼈나?

  • 평가 시스템은 단순한 점수 입력이 아니라, 제도·대상자·양식·결과·공개 타이밍이 모두 맞물려야 한다는 걸 구현하면서 실감했다.
  • 특히 캘리브레이션 단계를 설계할 때, "점수를 조정한다"는 행위가 단순한 필드 업데이트가 아니라 누가, 왜, 얼마나 바꿨는지를 추적해야 하는 책임 있는 작업임을 체감했다.
  • 목표 스냅샷을 평가 응답에 붙이는 구조를 만들면서, 처음에는 참조용으로 충분하다고 생각했지만 실제로는 목표 달성률이 점수 계산에 연결되지 않는 구조적 빈틈이 있다는 걸 인식했고, 그게 단순한 코드 문제가 아니라 설계 의도 자체가 불명확했기 때문이라는 걸 알았다.
  • 시스템이 커질수록 "어디까지 자동화하고 어디서 사람이 판단해야 하는가"라는 경계를 설계로 명확히 해야 한다는 점이 인상 깊었다.

3. Findings — 무엇을 배웠나?

  • 상태와 이벤트를 분리해서 관리해야 한다는 점을 배웠다. 시즌 status(DRAFT/ACTIVE/CLOSED)resultsPublishedAt을 별도로 관리한 것처럼, "시스템 내부 진행 상태"와 "외부에 노출되는 공개 상태"는 독립적으로 제어할 수 있어야 다양한 운영 시나리오를 수용할 수 있다.
  • JSON 컬럼의 남용은 타입 안전성 문제로 돌아온다는 점을 다시 확인했다. evaluatorMapsJson을 서비스에서 objectMapper.readValue()로 직접 파싱하고 map.get("targetMemberId")처럼 문자열 키로 접근하는 구조는, 오타가 있어도 컴파일 타임에 잡히지 않고 런타임 NPE나 예외로만 발견된다. KpiDefinition처럼 전용 값 객체와 AttributeConverter를 사용하는 패턴이 일관되게 적용되어야 한다.
  • 스냅샷은 "표시"와 "연산" 두 가지 목적을 구분해서 설계해야 한다는 점을 배웠다. goalSnapshotJson은 평가 당시 목표를 보여주는 데는 충분하지만, 평가 점수 계산에 achievementPct를 연산 입력으로 쓰려면 스냅샷에 achievementPctAtSnapshot, weightPct 같은 연산 가능한 수치가 명시적으로 포함되어야 한다. 목적이 다르면 저장 구조도 달라야 했다.
  • 두 파이프라인이 연결되지 않으면 시스템의 의미가 반감된다는 점을 설계 검토를 통해 배웠다. KPI 템플릿으로 목표를 만들고 달성률을 추적하는 성과 파이프라인과, 평가자가 직접 점수를 입력하는 평가 파이프라인이 현재는 완전히 분리되어 있다. 목표 달성률(achievementPct)이 EvaluationDesign의 KPI 섹션에 자동으로 반영되지 않으면, 정량적 성과 데이터를 쌓는 의미가 사라진다.
  • 감사 이력(Audit Trail)은 별도 테이블로 분리해야 한다는 원칙을 CalibrationHistory 설계를 통해 체득했다. 현재값을 덮어쓰는 것과, 변경 전·후를 모두 보존하는 것은 완전히 다른 요구사항이며, HR 시스템처럼 책임 추적이 중요한 도메인에서는 이력 테이블이 필수다.

4. Future — 다음에 어떻게 활용할까?

  • EvaluationDesign의 섹션 구조에 SectionType(MANUAL / KPI_SCORE / PEER_FEEDBACK)을 도입해, KPI 섹션은 achievementPct를 자동 집계하고 수동 섹션만 평가자가 직접 입력하는 혼합 채점 구조로 개선할 계획이다.
  • GoalSnapshotDtoachievementPctAtSnapshotweightPct를 추가해, 스냅샷이 화면 표시뿐 아니라 점수 계산의 입력값으로도 사용 가능한 구조로 확장할 예정이다.
  • evaluatorMapsJsonEvaluatorMapping 값 객체와 컨버터로 타입화해, 서비스 내 수동 파싱 코드와 중복 로직을 제거할 계획이다.
  • EvaluationResponsetargetGoalIdsJson 필드는 goalSnapshotJson에서 ID를 추출하면 대체 가능하므로 제거해 중복 저장 문제를 해소할 예정이다.
  • 최종적으로는 KpiTemplate → Goal → achievementPct → EvaluationResponse.score 로 이어지는 성과와 평가가 실질적으로 연결된 단일 파이프라인을 완성하는 것을 목표로 한다.

🧩 마무리 한 줄

"평가 시스템을 만들며 깨달은 것 - 점수를 저장하는 건 쉽지만, 그 점수가 어디서 왔는지 추적 가능하고 성과 데이터와 실제로 연결되어야 비로소 의미 있는 평가 시스템이 된다."

profile
Eazy하게

0개의 댓글