[BE 8기] 우테코 프리코스 오픈미션 회고

2026. 1. 25. 17:55·우아한테크코스

1. 목표 정하기

저는 오픈미션 목표를 크게 두 가지 세웠습니다.
첫째, 동물병원 환자 관리 시스템 개발하기
둘째, 환자 관리 시스템 기반으로 진료 소견서 자동 생성 기능 개발하기

Q. 어떻게 이 주제를 선정하게 되었는가?

동물병원와 함께하는 프로젝트에서 진료 소견서 자동 생성 시스템을 개발한 경험이 있습니다. 본 프로그램은 수의시가 진료를 마친 환자에 대한 상태를 작성하는 “진료 소견서”를 자동으로 만들어줍니다. 프로그램을 요약하자면 아래와 같이 동작을 합니다.

  1. PDF 형식인 환자 진료 차트에서 카테고리별로 데이터를 추출
  2. LLM과 이미지 모델을 활용해서 데이터를 요약
  3. 요약된 데이터를 pptx 형식의 진료 소견서 템플릿에 삽입

시스템을 개발할 당시에 동물병원 내의 환자 정보 시스템에 접근할 수 없었고 백엔드 지식이 전무했습니다. 그래서 웹이나 애플리케이션으로 제작하지 못했고, CLI 프로그램으로 제작을 했습니다. 로컬 파일 경로에 PDF가 들어오는 지 모니터링하고, 새로운 환자 차트 PDF가 디렉토리에 들어왔을 때 CLI 프로그램을 실행해 진료소견서(pptx)를 만들어주도록 제작했습니다. python monitory.py로 실행하는 콘솔 프로그램이다 보니 많은 아쉬움이 있었습니다. PDF에서 데이터를 추출해서 사용하는 것이 아니라 환자 정보 시스템에 저장된 데이터를 바로 사용하는 것은 어떨까?, 단순 콘솔 프로그램이 아니라 진료 소견 요약 시스템을 웹 서비스로 제작하면 어떨까?라는 호기심으로 해당 주제를 선정하게 되었습니다. 그리고 아직 웹 서버 개발 경험이 아예 없었기 때문에 이번 기회를 통해 경험하면서 배우는 시간으로 만들 수 있겠다고 생각했습니다.

  • 개발했던 진료 소견서 자동 생성 시스템

https://github.com/SineQunNon/PerfectS

 

GitHub - SineQunNon/PerfectS: 진료 소견서 자동생성 프로그램

진료 소견서 자동생성 프로그램. Contribute to SineQunNon/PerfectS development by creating an account on GitHub.

github.com

 

2. 계획 세우기 - 11월 5일(수)

목표를 세웠으니, 구체적으로 어떤 과정으로 목표를 달성하지에 대한 일정을 세웠습니다.

  1. 수의사 도메인 정리하기
  2. 요구사항 분석하기
  3. DB 테이블 정의서 작성하기
  4. 기능 명세서 작성하기
  5. 에러 코드 정의하기
  6. API 명세서 작성하기
  7. 1차 기능 구현하기(MVP) → 기본적인 CRUD 연산 구현하기
  8. 중간 점검하기(리팩토링)
  9. 2차 기능 구현하기 → AI 기능 추가하기!
  10. 회고 작성하기

 

3. 수의사 도메인 정리하기 - 11월 5일(수)

실제로 진료 차트 데이터에는 엄청나게 많은 종류의 데이터를 포함합니다. 실제 동물병원에서 사용하는 데이터를 모두 시스템에 적용하고 싶지만 일정상 모두 적용한다면 2주간 설계만 하다고 끝날 것 같다고 판단했습니다. 그래서 최대한 구체적인 내용은 빼고, 기본적인 내용만 간추려서 환자 정보 시스템을 제작하기로 했습니다.

 

4. 요구사항 분석하기 - 11월 5일(수)

유비쿼터스 언어라는 개념을 접했습니다. 환자 정보 시스템을 개발할 때 도메인 전문가 관점으로 작성하고, 개발자 관점에서도 작성하면서 두 개의 개념을 점차 맞춰지는 과정을 만들어보고 싶었습니다. 하지만 이미 제 머릿속은 개발자 입장이어서 그런지 도메인 전문가 관점으로 요구사항을 만드는 데는 한계가 있었습니다. 그래도 “~한 기능들이 있어야 해!” 느낌으로 작성을 했고, 그 다음에는 “만약에 이런 기능이 동작 안했을 때는 어떻게 해야 하지?”라는 접근 방식으로 요구사항 명세서를 작성해봤습니다.

 

[DDD] 유비쿼터스 언어(Ubiquitous Language)의 중요성

 

[수의사 관리]

  1. 수의사 등록
    1. 수의사 정보를 등록할 수 있다.
    2. 수의사명을 입력받는다.
  2. 수의사 조희
    1. 등록된 수의사 목록을 조회할 수 있다.

[환자(동물) 정보 관리]

  1. 환자 등록
    1. 환자 정보를 등록할 수 있다.
    2. 환자는 고유한 동물 번호를 가진다.
    3. 동물 이름, 보호자명, 품종, 체중, 생일을 입력 받는다.
    4. 환자 정보는 모두 포함되어야 한다
  2. 환자 조회
    1. 등록된 전체 환자 목록을 조회할 수 있다.
  3. 환자 정보 수정
    1. 동물 번호로 환자를 찾아서 정보를 수정할 수 있다.
    2. 동물 번호는 수정할 수 없다.
  4. 환자 정보 삭제
    1. 환자 정보를 삭제할 수 있다.

[진료 기록 관리]

  1. 진료 기록 작성
    1. 수의사는 환자에 대한 진료 기록을 작성할 수 있다.
    2. 진료 기록에는 날짜를 입력한다.
    3. SOAP 형식으로 진료 내용을 기록한다.
      1. S(Subjective): 주관적 판단
      2. O(Objective): 객관적 판단
      3. A(Assessment): 확정 진단명
      4. P(Plan): 추후 계획(치료 계획, 처방)
  2. 진료 기록 조회
    1. 특정 환자의 모든 진료 기록을 조회할 수 있다.
  3. 진료 기록 수정
    1. 작성된 진료 기록의 내용을 수정할 수 있다.
    2. 진료 날짜도 수정 가능하다.
  4. 진료 기록 삭제
    1. 특정 진료 기록을 삭제할 수 있다.

[진료 소견서 작성]

  1. 진료 소견서 작성
    1. 수의사는 특정 환자에 대한 진료 소견서를 작성할 수 있다.
    2. 환자 정보가 있는 환자에 대해서 작성이 가능하다.
    3. 다음 항목을 입력한다.
      1. Cheif Complaint(주요 증상)
      2. Assessment(확정 진단)
      3. Plan(치료 계획)
      4. Postoperative Care(추후 관리)
  2. 진료 소견서 조회
    1. 특정 환자의 진료 소견서를 조회할 수 있다.
  3. 진료 소견서 수정
    1. 작성된 진료 소견서의 내용을 수정할 수 있다.

5. DB 테이블 정의서 작성하기 - 11월 5일(수)

학부생 수업 이후로 오랜만에 DB 테이블을 정의하려다보니 많이 어색했습니다. DB 테이블 정의 방법을 여러 블로그를 활용해서 복습한 뒤에 정의서를 작성했습니다.

 

[참고 링크]

테이블 정의서 작성 가이드

개발자인데? 테이블 정의서 작성법 모르신다구요?

 

vet(수의사)

필드명 데이터 타입 길이 제약 조건 설명 null여부
vet_id INT   PK,AUTO_INCREMENT 기본 키 NOT NULL
vet_name VARCHAR 20   수의사명 NOT NULL

 

pet(환자)

필드명 데이터 타입 길이 제약 조건 설명 null여부
pet_id INT   PK,AUTO_INCREMENT 환자 번호(기본 키) NOT NULL
pet_name VARCHAR 20   환자 이름 NOT NULL
ower_name VARCHAR 20   반려인명 NOT NULL
breed VARCHAR 20   품종 NOT NULL
weight DECIMAL (5, 2)   체중 NOT NULL
birthdate DATE     생일 NOT NULL

 

medical_record(진료 기록)

필드명 데이터 타입 길이 제약 조건 설명 null여부
record_id INT   PK,AUTO_INCREMENT 기본 키 NOT NULL
pet_id INT   FK 환자 번호 NOT NULL
vet_id INT   FK 수의사 ID NOT NULL
record_date DATE     진료 날짜 NOT NULL
subjective VARCHAR 500   주관적 판단 NOT NULL
objective VARCHAR 500   객관적 판단 NOT NULL
assessment VARCHAR 200   확정 진단명 NOT NULL
plan VARCHAR 500   추후 계획 NOT NULL

 

medical_report(진료 소견서)

필드명 데이터 타입 길이 제약 조건 설명 null여부
report_id INT   PK,AUTO_INCREMENT 기본 키 NOT NULL
pet_id INT   FK 수의사 ID NOT NULL
vet_id INT   FK 환자 번호 NOT NULL
report_date DATE     작성 날짜 NOT NULL
chief_complaint VARCHAR 500   주요 증상 NOT NULL
assessment VARCHAR 200   확정 진단 NOT NULL
plan VARCHAR 500   치료 계획 NOT NULL
postoperative_care VARCHAR 500   추후 관리 NOT NULL
  • 수의사, 환자, 진료 기록, 진료 소견서가 있다.
  • 진료 기록의 하나의 수의사를 가질 수 있다.
  • 진료 기록의 하나의 환자를 가질 수 있다.

[ERD 다이어그램]

6. 기능 명세서 작성하기 - 11월 5일(수)

환자 정보 시스템이 가지는 주요 기능이랑 상세 동작들을 구체적으로 작성해보았습니다.

7. 에러 코드 정의하기 - 11월 5일(목)

에러 응답 형식

{
    "errorCode": "PET-001",
    "message": "환자 이름은 1자 이상 20자 이하여야 합니다.",
}

8. API 명세서 작성하기 - 11월 5일(수)~11월6일(목)

API 명세서 작성법을 블로그와 유튜브를 찾아보면서 공부했습니다.

API 명세서는 API의 기능, 동작, 사용법을 나타낸 문서입니다. 이름 및 설명, 기본 정보, 요청(request), 응답(respnose), 예시 정보가 포함됩니다. 기본 정보에서 Host 주소, endpoint, http method가 포함됩니다. 요청(request)에는 header정보, parameter, body가 포함됩니다. 응답(response) 정보에는 Header, body가 있습니다. API 명세서를 작성할 때 가장 중요한 것은 API 명세서만 보고도 사용할 수 있도록 만드는 것입니다. API 명세서 도구는 swagger, postman, rest docs, Notion 등이 있는데 현재 단계에서는 Notion이 제일 편한 것 같아 Notion으로 작성하기로 했습니다.

 

[10분 테코톡] 체인저의 API 명세서와 협업

API 명세서

 

9. 1차 기능 개발하기

도메인 정리부터 API 명세서 작성까지 마쳤습니다. 이제 스프링으로 구현을 시작하려 했지만, 어디서부터 손을 대야 할지 막막했습니다. 올해 초 3월에 스프링과 JPA 강의를 들었지만, 실제로 프로젝트를 만들어 본 경험이 없어서 시작점을 찾기가 어려웠습니다. 게다가 스프링과 JPA 개념도 시간이 많이 지나서 많이 흐릿해진 상태였습니다. 그래서 금요일부터 일요일까지 3일간 개념을 복습하는 시간을 가졌습니다. 더불어 일정을 조정했습니다.

11월 10일(월) - 도메인 구현하기

Q. 왜 스프링으로 구현하는 것을 선택했나요

스프링을 조금이나마 공부한 경험은 있지만, 실제로 제 손으로 서비스를 처음부터 끝까지 만들어 본적이 없었습니다. 이번 기회에 작은 프로젝트라도 전체 개발 과정을 경험해보고 싶었습니다. 또한 이전 진료 소견서 생성 시스템에서는 LangChain을 사용했는데, 이번에는 SpringAI를 활용해 구현해보고 싶은 생각도 들었습니다. 이러한 이유로 스프링을 선택하게 되었습니다.


오픈 미션 진행 방식 중에서 프리코스 경험을 기반으로 관련된 분야의 미션 설계하고 구현한다는 조건이 있었기 때문에, 내가 프리코스를 하면서 배운 내용을 적용해보고자 했습니다.

첫째, READMD.md를 상세히 작성하려고 했습니다. 구현해야 할 기능 목록을 중심으로 먼저 작성했고, 예외 상황도 함께 정리했습니다. 그리고 기능을 구현하면서 꾸준히 READMD.md를 업데이트하기로 했습니다.

둘째, 프리코스 기간 중에 테스트를 작성하는 법을 배웠기 때문에, 테스트를 작성하고 기능을 구현하는 방법을 배웠기 때문에 이를 적용해보기로 했습니다.

그래서 테스트 작성 → 기능 구현 → README.md 업데이트 → 커밋 과정을 반복했습니다. (지금 생각해보면 과할 정도로 많은 커밋을 한 것 같다.)

어디서부터 테스트 코드 작성을 시작해야 할 지 고민을 했습니다. API 명세서 한 개 당 하나씩 기능을 구현해야 하나..? 그렇다기엔 하나의 API 안에는 여러 개의 도메인이 섞여있는 경우가 있어서 작은 테스트를 수행하기는 무리가 있는 것 같았습니다. 여기서 가장 작은 단위는 도메인이니깐, 도메인 하나에 대한 테스트를 작성하고 도메인 하나를 구현하기로 했습니다.

[ 익힌 개념 ]

@Entity 애노테이션을 사용했을 때, protected 생성자와 public 생성자를 동시에 선언해야 했다. 이를 통해 의미 없는 객체를 만들어 버그가 발생하는 것을 방지할 수 있습니다.

@Entity
public class Vet {
        protected Vet() {}

        public Vet(String vetName) {
                this.vetName = vetName;
        }
}

//또는

@Entity
@NoArgsConstructor(access = AccessLevel.protected)
public class Vet {
        public Vet(String vetName) {
                this.vetName = vetName;
        }
}

 

@GeneratedValue 애노테이션은 기본 키 값을 자동으로 생성하도록 도와준다.

생성전략

  1. IDENTITY: DB의 AUTO_INCREMENT 기능 사용
  2. AUTO: Hibernate가 알아서 선택하도록 함

이렇게 일주일이 지났습니다...


11월 11일(화) - Repository 구현하기

도메인 구현을 마쳤습니다.

https://github.com/sinequanonn/veterinary-clinic/pull/1

 

도메인 구현 완료 by sinequanonn · Pull Request #1 · sinequanonn/veterinary-clinic

도메인 Vet 엔티티 구현 수의사명 유효성 검증 Pet 엔티티 구현 환자명 유효성 검증 보호자명 유효성 검증 품종 유효성 검증 체중 유효성 검증 생년월일 유효성 검증 MedicalRecord 엔티티 구현 진료

github.com

 

구현을 마친 뒤 다시 돌아보면서 느낀 점은

첫째, 도메인 계층에서 할 필요 없는 테스트를 작성했다는 생각이 들었습니다. 예를 들어 “수의사명이 20자를 초과하면 예외가 발생한다”는 책임은 도메인 계층에서 담당하는 것이 아니라, 서비스 계층에서 수행돼야 하는 게 적절해보였습니다. 그래서 전체적으로 대부분 테스트 코드가 서비스 쪽에서 테스트를 수행하는 것이 적절해보였습니다.

둘째, 엔티티를 구현할 때 @Entity 애노테이션을 사용했지만 JPA 테스트를 작성하지 않고, POJO 방식으로 테스트를 진행했습니다. 즉, POJO 클래스에 아직은 필요하지 않은 @Enttiy를 일단 붙이고 테스트를 진행했던 것이었습니다. 여기서 든 의문점이 “POJO 클래스에 @Entity를 붙인다고 해서 왜 테스트가 깨지지 않을까?”였습니다. 더 나아가서 어떤 규칙을 가졌길래 테스트가 깨지지 않았을까? 그리고 만약 깨진다면 어떤 부분을 준수하지 않았기에 테스트가 깨지는 문제가 발생할까?라는 질문을 던지게 되었습니다.

JPA 엔티티를 POJO 클래스처럼 테스트할 수 있는 이유는 POJO 클래스가 담당하는 로직이 JPA 영역에 침법하지 않기 때문이라고 생각했습니다. @Entity 애노테이션을 붙이기 전까지는 POJO 클래스에 아무런 영향이 없다가, @Entity를 붙이는 순간 부가 기능이 추가되는 것입니다. 사실상 위임자가 생기는 것이고, 그 위임자가 자신의 역할을 수행하는 데 그 범위를 벗어나지 않았기 때문에 오류가 나지 않은 것입니다. 즉, 관심사 분리 원칙을 잘 지키고 있었던 것입니다. 다르게 말하면 테스트 범위를 잘 제한했기 때문에 오류 없이 테스트할 수 있었습니다.

더 나아가서 만약, JPA에서 대포적인 오류 형태가 바로 Lazy 로딩인데, POJO 형태에서는 절대로 테스트가 불가능한 형태이다. 이렇게 테스트가 가능한 범위와 불가능한 범위를 생각하면서 코드를 구현하면, 각 계층의 책임을 분명히 이해하고 적절한 테스트를 할 수 있을 것이라 생각했습니다.

도메인 구현을 마치고 나서 h2 데이터베이스를 설치하고, 의존성도 추가하였습니다.

Service 계층을 구현하면서 도메인 계층을 구현할 때처럼 단위테스트를 작성하려고 했습니다. 하지만 Service는 VetRepository에 직접 의존하고 있었고, 이 Repository는 JpaRepository를 상속받고 있었습니다.

public class VetService {
    private final VetRepository vetRepository;

    public VetService(VetRepository vetRepository) {
        this.vetRepository = vetRepository;
    }
}

여기서 의문이 들었습니다.

“Repository에 직접 의존하고 있는 상태에서 단위 테스트를 작성하는 게 맞을까”

처음에는 @SpringBootTest를 사용하면 이미 구현된 JpaRepository를 그대로 사용할 수 있어서 편하다고 생각했습니다. 하지만 아니었습니다.

조사해보니 @SpringBootTest는 전체 애플리케이션을 띄우고 실제 DB연결하면서 외부 의존성이 포함되기 때문에 통합 테스트용 애노테이션이었습니다. 그렇기 때문에 단위 테스트를 작성하기 위해서 외부 의존성을 제거해야 했습니다.

더 알아보니 Mocking을 사용하면 실제 JpaRepository 구현체 없이 테스트가 가능하다는 것을 알게 되었습니다. 여기서 더 나아가 새로운 의문이 들었습니다. Repository 자체는 테스트 하지 않아도 되나?

제가 생각한 정답은 “해야 한다”였습니다. 그리고 여기서 중요한 점은 어디까지 테스트해야 하는가에 대한 기준이었습니다. 기준은 생각보다 쉽게 세울 수 있었습니다. 제가 프리코스 기간동안 도메인과 서비스에 대한 기준을 고민한 적이 있었습니다. “하나만 처리할 때는 도메인, 두 개 이상을 처리하면 서비스”입니다.

Repository는 도메인 계층에 포함된다고 생각했기에, Repository에 대한 테스트는 저장, 조회, 수정, 삭제를 하고, Service 계층에서는 두 개 이상의 도메인을 활용한 로직에 대한 테스트를 작성하면 되겠다고 생각했습니다.

JpaRepository는 @DataJpaTest 애노테이션을 활용해 단위 테스트를 진행할 수 있다고 알게 되었습니다.

https://github.com/sinequanonn/veterinary-clinic/pull/2

 

도메인 Repository 구현 완료 by sinequanonn · Pull Request #2 · sinequanonn/veterinary-clinic

 

github.com

 

11월 12일(수) - Service 구현

Repository 계층 구현 및 테스트 작성이 끝나서 Service 계층 구현을 시작했습니다.

구현을 시작하기 전에 Mock 사용법을 공부했습니다.

Mock 사용법 익히기

VetService를 작성하면서, 현재 서비스 기능 요구사항은 대부분 Repository 게층에서 수행하는 작은 단위이므로 서비스 계층에서 한 번 더 테스트할 필요가 없다고 판단했습니다. 즉, 하나의 도메인에 대한 기능 테스트 담당은 Repository에서 이미 하고 있기 때문에 서비스 계층에서 한 번 더 할 필요가 없다고 생각했습니다. 그렇기 때문에 Repository를 활용한 Service 게층 코드를 구현했습니다.

https://github.com/sinequanonn/veterinary-clinic/pull/3

 

251112 morning by sinequanonn · Pull Request #3 · sinequanonn/veterinary-clinic

Mock 테스트 작성법 공부 VetServiceTest, VetService 작성

github.com

 

 

 

서비스 계층까지 모두 구현을 마치고 컨트롤러를 구현할 차례가 되었습니다. 이 때 “컨트롤러를 단위테스트로 작성해야 할까, 통합 테스트로 작성해야 할까?”라는 고민이 생겼습니다.

제 상황은 이미 하위 계층들이 모두 구현되어 있고 테스트도 완료된 상태였습니다. 저는 이미 바텀업 방식으로 구현하고 있었기에 이미 테스트가 완료된 계층을 활용하는 것이 더 적합하다고 판단했습니다.

만약 탑다운 방식으로 개발했다면, 서비스가 아직 구현되지 않은 상태이므로 @WebMvcTest를 사용해서 컨트롤러에 대해서만 단위 테스트를 했을 것입니다. 하지만 현재 상황은 모든 계층이 준비되어 있기 때문에 @SpringBootTest를 사용한 통합 테스트가 더 적합하다고 판단했습니다.

이 경험을 기반으로 통합 테스트를 선택하는 경우와 단위 테스트를 선택하는 기준을 세울 수 있었습니다.

  • 통합 테스트(@SpringBootTest)를 선택하는 경우
    • 바텀업 방식이고 하위 계층이 이미 구현돼 있을 때
    • 전체 플로우가 제대로 연동되는지 확인하고 싶을 때
  • 단위 테스트(@WebMvcTest)를 선택하는 경우
    • 탑다운 방식으로 아직 하위 계층이 구현되지 않았을 때
[ 익힌 개념 ]

@AutoConfigureMockMvc

: MockMvc 빈을 자동으로 구성한다. 톰캣 같은 외부 서버를 띄우지 않고, HTTP 요청/응답을 시뮬레이션 할 수 있게 해준다.

mockMvc.perform(…)

: 실제 컨트롤러에 요청을 하는 것과 동일한 흐름을 검증할 수 있게 해준다.

 

11월 13일(목) - Controller 구현

컨트롤러를 구현할 때 브랜치를 안 만들고, 제 다른 계정으로 커밋하는 실수를 범했습니다.

Comparing ed28471...727bc8c · sinequanonn/veterinary-clinic

여기까지 1차 기능 구현이었고, 기능 구현을 마친 뒤 제가 했던 과정에 대해 돌아보는 시간을 가졌습니다.

그리고 여기서 과제가 17일까지 제출인 줄 알고 17일까지로 계획을 세웠다가, 과제 제출 마감이 25일이라는 것을 알게 되었습니다.

여기서 저의 고민은 다음과 같습니다.

  1. 추가적인 요구사항을 나열해서, 기존 코드 위에서 복잡한 비즈니스 로직을 추가 개발해야 하는가
    1. 스스로 왜 추가해야 하는가?라는 질문에 1차 기능 요구사항에는 단순 CRUD 연산만 포함했고, 복잡한 비즈니스 로직이 포함되지 않았습니다. 그래서 복잡한 비즈니스 로직을 다루는 서비스 계층을 구현해보고 싶은 생각이 들었습니다.
  2. 기존 코드에서 보완할 부분을 찾고 리팩토링을 진행하는가
    1. 코드를 구현하는 과정에서, 도메인 계층에서 서비스 계층의 책임을 담당하고 있었고, POJO 방식으로 구현했으며, JPA를 제대로 활용하지 못했다고 생각을 했습니다. 그리고 특히 엔티티에서 Getter를 사용하고 있는 것에 대한 찝찝함이 게속 있었습니다.
    2. 예외 처리도 새롭게 정의해서 구현을 시작하기 전에 만들엇던 에러 코드를 사용하고 싶다는 생각을 했습니다.
    3. 서버 구현한 경험이 없다보니, response body에 정확히 어떤 데이터가 들어있어야 하는지 판단하기 힘들었습니다. 다시 정확하게 공부하고 적용해보고 싶은 생각이 들었습니다.
  3. 지금 바로 SpringAI를 추가 해야 하는가?

결론은 현재 코드가 깔끔하지 않다고 판단했고, 이 상태로 추가 기능을 구현한다면 마지막에 리팩토링 하는 과정이 더 부담된다고 판단했습니다. 그래서 리팩토링을 먼저 하기로 선택했습니다.

리팩토링 대상

  • 검증을 API 앞에서 처리하는 로직 추가하기
  • controller와 Service 계층에서 dto의 정적 팩토리 메서드 사용하도록 변경하기

더불어 테스트 작성을 postman으로 해보기로 했습니다.

그래서 먼저 다음 일정을 조정했습니다.

날짜 활동 산출물
11/5(수) ~ 11/6(목) 기존 도메인 정리요구사항 분석기능사항 정의DB 테이블 정의서 작성에러코드 정의API 명세서 작성 요구사항명세기능명세서에러코드 정의서DB테이블 정의서API 명세서
11/7(금) ~ 11/9(일) 스프링, JPA 복습 -
11/10(월)~11/12(수) 1차 기능 개발 핵심 기능 코드
11/13(목) 중간 점검 진행률 표?
11/13(목)~11/15(토) 리팩토링 리팩토링 적용 코드
11/15(토)~11/16(일) 에러 처리 로직 추가하기 에러 처리 추가 코드
11/17(월)~11/18(화) Spring AI를 활용한 진료소견서 요약 기능 추가 ai 추가 코드
11/19(수)~11/24(월) 회고 작성하기  
제출 회고글  
최종 산출물(코드)    

 

10. 리팩토링 - 11월 14일(금)~11월 15(토)

https://github.com/sinequanonn/veterinary-clinic/pull/4

 

리팩토링 by sinequanonn · Pull Request #4 · sinequanonn/veterinary-clinic

DTO 계층 재구조화 DTO에 Bean Validation을 통한 검증 추가 정적 팩토리 메서드 및 Builder 패턴 적용 서비스/컨트롤러 책임 명확히 분배

github.com

 

  1. DTO 계층을 재구조화을 했고, 책임을 분리했습니다.
  • 단일 DTO를 용도별로 분리했습니다(예: VetRequest → VetCreateRequest)
  • 생성, 수정, 조회와 같이 목적에 맞는 DTO를 각각 정의했습니다.
  1. 직관적인 네이밍을 사용하도록 수정했습니다.
  2. Bean Validation을 통한 검증 로직을 추가했습니다.
  3. Builder 패턴을 적용했습니다.
  4. 계층 간 책임을 명확하게 분배했습니다.
    1. 원래는 서비스 계층에서 엔티티를 직접 받고 반환했지만
    2. Request DTO를 받아 Response DTO를 반환하도록 수정했습니다.
    3. 컨트롤러가 DTO를 엔티티로 직접 변환하던 로직을 서비스에게 위임했습니다.
    4. → 이로써 컨트롤러는 HTTP 요청/응답 처리하는 책임에만 집중할 수 있게 했습니다.

리팩토링을 마무리하고 나니, 그동안 무분별하게 사용했던 Lombok 애노테이션들에 의문이 생겼습니다. @Builder, @AllArgsConstructor, @NoArgsConstructor 같은 애노테이션들을 명확한 기준 없이 "일단 편하니까" 사용하는 경향이 있었습니다. 이제는 각 애노테이션을 언제, 왜 사용해야 하는지 기준을 명확히 하고 다시 적용해야겠다고 생각했습니다.

Lombok 애노테이션 사용 기준 정리

데이터 수정 기능을 구현하면서 JPA의 변경 감지를 사용했습니다. 그런데 update 로직을 어디에 위치시켜야 할지 고민이 되었습니다.

  1. DTO 내부에서 엔티티의 update 메서드를 호출해야 하는가?
  2. Service 계층에서 udpate 메서드를 호출해야 하는가?

해당 고민은 빠르게 해결할 수 있었습니다.

프리코스를 진행할 때 “객체 생성을 컨트롤러에서 해야 하는가 아니면 서비스에서 생성해야 하는가”에 대한 고민을 한 적이 었었습니다. 그 때 제가 내린 결론은 “생성하는 것은 비즈니스 로직이므로 서비스 계층이 담당해야 한다”였습니다. “로또를 발행한다”가 비즈니스 로직이듯이 “이름을 변경한다”도 비즈니스 로직이므로 서비스 계층에 있는 것이 적절하다고 판단했습니다.

 

11. 에러 로직 추가 - 11월 16일(일)

기존에 정의했던 에러 코드를 적용하기로 했습니다. 그전에 스프링에서 에러 처리 동작 방식을 먼저 학습했습니다.

스프링에서 API 예외 처리 학습

https://github.com/sinequanonn/veterinary-clinic/pull/5

구현 과정에서..

처음에는 기존에 정의했던 에러 코드를 모든 예외 상황에 적용하려고 했습니다. 하지만 구현하다 보니 너무 too much라는 것을 깨달았습니다.

특히 DTO 검증 로직에 에러 코드를 적용하는 것이 매우 부담스러웠습니다. 그래서 굳이 DTO 검증 로직에 대해서 에러 코드를 적용하는 건 오버 엔지니어링이라고 판단했습니다. Bean Validation의 기본 메시지만으로도 충분히 명확하게 어떤 오류인지 판단 가능하다고 생각했습니다.

결론은 비즈니스 로직에만 에러 코드를 적용하기로 했습니다.

에러 로직을 추가하고 postman으로 테스트도 진행했습니다.

 

12. Spring AI 추가 - 11월 17일(월)~11월 18일(화)

드디어 Spring AI를 활용해서 환자 진료 데이터를 기반으로 진료 소견서를 자동으로 생성하는 기능을 추가하기로 했습니다.

기존 시스템은 PDF 파일에서 카테고리 별로 데이터를 추출하는 방식이었습니다. 하지만 현재는 환자 진료 기록이 환자 시스템에 저장돼 있는 상황입니다.

기존에는 환자 정보, 동물병원 정보, 코드, 항목명, 수량, 일투, 일수, 총투, 확정 진단명, 구분, 코드, 과목, vital check, lab 표 데이터 등 어마어마하게 많은 데이터가 존재합니다. 하지만 현재는 축약된 데이터를 기반으로 진료 소견서에 들어갈 내용을 자동으로 생성하도록 구현하는 것이 목표입니다.

더불어 RAG 프로세스를 적용하고 싶었지만, 실제 동물 병원의 데이터를 기반으로 VectorDB를 구축하는 데 무리가 있다고 판단했습니다. 그렇기 때문에 에시 프롬프트를 주어서 진료 소견서를 생성하도록 구현하기로 했습니다.

다음과 같은 프로세스로 진행됩니다.

  1. petId와 recordId를 활용해서 진료 기록을 조회합니다.
  2. 진료 기록을 LLM에 입력 가능한 컨텍스트로 변환합니다.
  3. 컨텍스트 + 지시사항을 LLM에게 주입합니다.
  4. OutputConverter에 따라 진료 소견서 카테고리에 데이터를 자동으로 삽입합니다.

LangChain을 활용한 경험은 있지만 Spring AI를 활용한 경험이 없기 때문에 기본적인 Spring AI에 대해 학습했습니다.

Spring AI 학습하기

https://github.com/sinequanonn/veterinary-clinic/pull/6


프리코스를 마치면서

전체적으로 돌이켜보면, 이번 오픈 미션은 저에게 “시스템 구현 했다”라는 사실 그 이상이었던 것 같습니다. 도메인을 정의하는 과정, 테스트의 기준을 세우는 과정, 검증과 예외 처리를 분리하는 과정, 리팩토링을 통해 구조를 다듬는 과정, 그리고 AI 기능을 서비스와 연결하는 과정까지, 모든 과정이 배움이었습니다. 설계부터 개발, 테스트, 리팩토링까지 한 번의 사이클을 직접 온전하게 경험해본 것이 저에게 가장 큰 수확이었던 것 같습니다.

이렇게 5주 프리코스 기간 전체가 마무리되었을 때 정말 후련한 마음이 들었습니다. 동시에 정말 멋진 개발자가 되고 싶다는 마음이 들었습니다. 저는 원래 무언가를 배우고 나서 정리하는 성격이 아니었습니다. 그냥 이해하면 넘기고, 필요하면 다시 찾는 식이었습니다. 그런데 프리코스에서는 매주 제가 배운 것을 글로 남기고, 다른 사람의 생각도 보고 제 생각을 비교한 뒤에 다시 정리해서 공유해야 했습니다. 이 과정은 처음에는 정말 어색했고 시간도 많이 걸려서 힘들었습니다. 하지만 하루하루 하다 보니 많은 것을 깨닫게 되었습니다. 결국 개발자는 혼자 컴퓨터 앞에 앉아서 코드만 치는 사람이 아니라 문제를 해결해야 하는 사람인 것을 다시 느꼈습니다. 제 생각을 동료들에게 설명할 수 있어야 하고, 동료들의 생각을 이해하는 것이 중요하다는 사실을 배웠습니다. 이런 경험이 쌓이면서, 단순히 기술을 아는 개발자가 아니라 함께 일하고 함께 성정할 수 있는 개발자가 되고 싶다는 목표가 생겼습니다.

 


** 미션하면서 노션에 정리했던 거...

https://www.notion.so/2af8273095b180b1bac6c77f8d96c372?source=copy_link

 

오픈 미션 | Notion

동물병원 환자 시스템 with 진료 소견서 자동 생성

www.notion.so

 

**블로그 새로 만들어서 프리코스 기간 때 작성했던 내용을 다시 옮겨 적었습니다.

https://velog.io/@sinequanonn/BE-8%EA%B8%B0-%EC%9A%B0%ED%85%8C%EC%BD%94-%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-4%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0

https://velog.io/@sinequanonn/BE-8%EA%B8%B0-%EC%9A%B0%ED%85%8C%EC%BD%94-%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-5%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0

 

'우아한테크코스' 카테고리의 다른 글

[우아한테크코스] 8기 BE 최종 합격 후기(+자소서)  (0) 2026.07.04
[BE 8기] 우테코 프리코스 3주차 회고  (0) 2026.01.25
[BE 8기] 우테코 프리코스 2주차 회고  (0) 2026.01.25
[BE 8기] 우테코 프리코스 1주차 회고  (0) 2026.01.25
'우아한테크코스' 카테고리의 다른 글
  • [우아한테크코스] 8기 BE 최종 합격 후기(+자소서)
  • [BE 8기] 우테코 프리코스 3주차 회고
  • [BE 8기] 우테코 프리코스 2주차 회고
  • [BE 8기] 우테코 프리코스 1주차 회고
sinequanon
sinequanon
  • sinequanon
    sinequanon
    sinequanon
  • 전체
    오늘
    어제
    • 분류 전체보기 (5)
      • 우아한테크코스 (5)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    프리코스
    우아한테크코스
    최종합격
    백엔드
    우테코 8기
    8기
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
sinequanon
[BE 8기] 우테코 프리코스 오픈미션 회고
상단으로

티스토리툴바