Post-Math 백엔드 개발자 채용의 과제 전형에 사용하는 저장소입니다. 실행 환경만 세팅되어 있고, 도메인 코드는 비어 있습니다.
- 과제 내용:
ASSIGNMENT.md - 문의: 한상준 · jphan32@postmath.co.kr
이 저장소를 보셨다고 해서 바로 시작하시면 안 됩니다. 과제는 지원 의사를 확인하고 저희가 수락한 분께만 진행합니다.
1. 자사 채용 페이지 또는 채용 공고를 통해 지원
↓
2. 서류 심사
↓
3. 통과 시 개별 연락 — 과제 안내 + 면접관 GitHub 계정 전달
↓
4. 지원자가 이 저장소로 본인 비공개 저장소 생성 + 전달받은 계정 초대
↓
5. 저희 확인 후 **기한 안내** → 마감 (A: 안내드린 일시 / B: 최종 제출 알림 시점)
저희는 과제 기간 내내 질문에 응답하고 PR을 리뷰합니다. 그래서 동시에 진행할 수 있는 인원에 한계가 있습니다. 누구나 시작할 수 있는 구조로 두면 응답 품질이 떨어지고, 그 피해는 성실하게 진행하시는 분께 돌아갑니다.
초대하실 면접관 계정 목록은 이 문서에 없습니다. 지원이 수락되면 안내 메일로 전달드립니다.
문의용 이메일에서 계정을 유추해 초대하지 말아 주세요. 초대 대상은 안내드린 목록이 전부이고, 그 외 초대는 확인 없이 거절됩니다.
마감 시점까지는 몇 번이든 자유롭게 고치고 다시 올리실 수 있습니다. 커밋을 몇 번 했는지, 몇 번 갈아엎었는지는 보지 않습니다.
마감이 언제인지는 두 가지 방식이 있고, 과제 안내 메일에 어느 쪽인지 적혀 있습니다.
| 마감 | |
|---|---|
| A. 마감일 지정 | 안내드린 날짜와 시각(KST). 그 시점의 main 상태를 가져갑니다 |
| B. 자율 제출 | "최종 제출합니다"라고 알려 주신 시점. 알려 주시기 전까지는 채점을 시작하지 않습니다 |
마감 이후에도 저장소를 계속 다듬으시는 것은 자유입니다. 다만 평가는 그 시점의 상태로 합니다.
최종 결과에서 불합격 판정을 받으신 경우에만, 판정일로부터 6개월간 재지원을 받지 않습니다. 같은 과제로 다시 평가하는 것이 서로에게 의미가 없기 때문입니다. 6개월이 지나면 과제가 개편되어 있으므로 새로 지원하실 수 있습니다.
- 판정 전에 중단하시거나 제출하지 않으신 경우에는 제한이 적용되지 않습니다. 사정이 생기면 편하게 알려 주세요.
- 미완성 제출을 걱정해 중단하실 필요는 없습니다 — 못 하신 부분을 적어 주시면 그 부분은 평가에서 제외합니다(
ASSIGNMENT.md§5). - 서류 심사에서 반려된 경우에도 제한이 없습니다.
- 저희 사정으로 전형이 중단된 경우에도 제한이 없습니다.
상단의 Use this template → Create a new repository
| 항목 | 설정 |
|---|---|
| Owner | 본인 계정 |
| Repository name | 자유 |
| Visibility | Private ✅ |
| Include all branches | 체크 해제 |
Fork가 아니라Use this template입니다. 공개 저장소를 fork하면 비공개로 만들 수 없습니다. 템플릿으로 만들면 완전히 독립된 저장소가 되고 커밋 하나로 시작합니다.
CLI를 선호하시면:
gh repo create <저장소명> --private --template <이 저장소> --clone저장소 → Settings → Access → Collaborators → Add people
안내를 받으신 뒤에 시작해 주세요. 저희가 기한과 마감 방식을 알려드리기 전에 임의로 진행하고 제출하시면, 저희 쪽에 채점 일정이 잡혀 있지 않아 평가에서 제외될 수 있습니다. 진행 인원을 관리하기 위한 절차이지 지원자를 거르기 위한 것이 아니니, 먼저 연락 주시면 됩니다.
자세한 절차는 ASSIGNMENT.md §2에 있습니다.
docker compose up -d --build # 또는 make up
curl localhost:8080/healthz # {"status":"ok"}기동 순서는 PostgreSQL → 마이그레이션 적용 → API 입니다. .env 없이도 기본값으로 뜹니다.
make help # 사용 가능한 명령 전체
make logs # 로그 보기
make psql # DB 접속
make reset # 볼륨까지 지우고 초기화
make test # go test ./...로컬에서 Go 프로세스만 직접 띄우려면 (DB는 도커 것 사용):
docker compose up -d db
make run # 마이그레이션을 먼저 적용한 뒤 API를 띄웁니다| 항목 | 위치 |
|---|---|
| Gin 엔진 구성, graceful shutdown | internal/server/server.go |
| GORM 연결, 커넥션 풀, SQL 로그 레벨 | internal/database/database.go |
| 환경변수 설정 로딩 | internal/config/config.go |
| 수동 DI 배선 지점 | cmd/api/main.go |
| PostgreSQL + 마이그레이션 + API의 compose 오케스트레이션 | docker-compose.yml |
| golang-migrate 기반 마이그레이션 러너 | migrations/, Makefile |
| 헬스체크 엔드포인트 | GET /healthz |
| 멀티스테이지 Dockerfile | Dockerfile |
| 항목 | 비고 |
|---|---|
| DB 스키마, 제약, 인덱스 | make migrate-create name=... 로 새 파일을 만들어 작성 (아래 주의 참고) |
| 도메인 모델과 레이어 설계 | internal/ 아래 구조는 바꿔도 됩니다 |
| API 설계 일체 (경로·메서드·상태코드·요청/응답·에러 처리) | registerRoutes() 에서 시작 |
| 트랜잭션을 어디서 열고 어떻게 전파할지 | 아래 대응표 참고 |
| 과제 명세의 요구사항을 만족시키는 방법과 그 증명 | ASSIGNMENT.md §4 |
| 검증 수단 (테스트 / .http / 컬렉션) | test/ 또는 원하는 위치 |
문서 (README, ERD, 시퀀스, API 명세, DECISIONS.md) |
evaluation.yaml은 채점 대상이 아닙니다. 어디에 무엇을 만드셨는지 저희에게 알려주는 안내 파일이고, 채우기 어려운 칸은 비워두셔도 됩니다. 비어 있으면 저희가 코드를 열어 직접 확인합니다 (ASSIGNMENT.md§4.3).
한 줄로 정리하면
internal/config·database·server바깥은 전부 과제입니다.
이 저장소의 코드는 마음대로 고치셔도 됩니다. 구조를 바꾼 것은 평가에 반영하지 않습니다. 바꾼 이유만 한 줄 남겨 주세요.
cmd/api/ 진입점, 의존성 배선
internal/
config/ 환경변수 설정 [제공]
database/ GORM 연결 [제공]
server/ Gin 엔진, 라우팅 [제공 + 과제]
domain/ 도메인 모델 [과제]
repository/ 데이터 접근 [과제]
service/ 비즈니스 로직 [과제]
handler/ HTTP 핸들러 [과제]
migrations/ SQL 마이그레이션 [과제]
test/ 테스트, 검증 스크립트 [과제]
evaluation.yaml API 위치 안내 [안내용 · 선택]
evaluation.yaml은 왜 있나요? API 설계가 자유이다 보니, 어떤 경로에 무엇을 만드셨는지 저희가 알 수 없으면 동작을 확인할 수 없습니다. 이 파일이 그 간극을 메웁니다. 주석을 따라 채우시면 되고, 만들지 않은 항목은 지우시면 됩니다. 자세한 설명은ASSIGNMENT.md§4.3에 있습니다.
이 구조는 출발점입니다. 다르게 가는 편이 낫다고 판단하시면 바꾸시고, 이유만 남겨 주세요.
익숙한 것과 다른 지점만 추렸습니다. 답을 적어둔 것이 아니라, 어디가 다른지만 표시한 것입니다.
| Spring / JPA | 여기서는 |
|---|---|
@Component / @Autowired |
컨테이너가 없습니다. cmd/api/main.go에서 생성자를 직접 호출해 배선합니다. |
application.yml + @ConfigurationProperties |
internal/config 에서 환경변수를 읽습니다. |
@RestController, @RequestMapping |
internal/server/server.go 의 registerRoutes() 에 라우트를 등록합니다. |
@Valid + Bean Validation |
Gin의 ShouldBindJSON + 구조체 binding:"..." 태그. 검증 실패 메시지를 어떻게 응답으로 바꿀지는 직접 정해야 합니다. |
@Transactional |
없습니다. db.Transaction(func(tx *gorm.DB) error {...}) 으로 직접 열고, tx를 하위 레이어까지 인자로 전달해야 합니다. 이 전파 구조를 어떻게 설계할지가 과제의 일부입니다. |
| 영속성 컨텍스트 / dirty checking | 없습니다. 변경은 명시적으로 Save / Updates 를 호출해야 반영됩니다. |
lazy loading (FetchType.LAZY) |
없습니다. GORM의 Preload는 lazy loading이 아니라 별도 SELECT를 추가로 실행하는 eager loading입니다. 프록시 객체가 없으므로 "쓰는 순간 쿼리가 나가는" 일도 없습니다. |
spring.jpa.show-sql |
DB_LOG_LEVEL=info (기본값). 실행되는 SQL이 전부 로그에 찍힙니다. |
| Flyway / Liquibase | golang-migrate. make migrate-create name=... |
@DataJpaTest, @SpringBootTest |
없습니다. httptest + 실제 DB(도커) 조합이 일반적입니다. testcontainers-go를 써도 됩니다. |
이 표는 언어·프레임워크가 다른 지점만 적은 것입니다. 설계 판단이 필요한 부분(트랜잭션 경계, 조회 전략, 동시성 처리 방식 등)은 과제 범위이므로 여기에 적지 않았습니다.
Updates()에 zero value를 넘기면 조용히 무시됩니다. 구조체로 넘길 때0,"",false는 "변경 없음"으로 취급됩니다.map[string]any를 쓰거나Select()로 컬럼을 명시해야 합니다.Find는 결과가 없어도 에러가 아닙니다. 단건 조회First만gorm.ErrRecordNotFound를 돌려줍니다.SkipDefaultTransaction: true로 설정해 두었습니다. 단건 쓰기마다 자동으로 붙던BEGIN/COMMIT이 없어 로그가 깨끗하지만, 트랜잭션이 필요한 곳은 반드시 직접 열어야 합니다.- 시간 컬럼은
timestamptz와timestamp가 다르게 동작합니다. 어느 쪽을 쓸지 정하고 이유를 남겨 주세요. - 이미 적용된 마이그레이션 파일을 고쳐도 다시 실행되지 않습니다. golang-migrate는 적용된 버전을
schema_migrations테이블에 기록하므로,000001_init.up.sql을 수정해CREATE TABLE을 넣어도 무시됩니다. 스키마 변경은 항상make migrate-create로 새 파일을 만드세요. (Flyway/Liquibase와 같은 원칙입니다.)- 마이그레이션이 중간에 실패하면
dirty상태로 남아 이후 실행이 전부 막힙니다.make migrate-version으로 확인하고,make migrate-force version=<직전버전>으로 푼 뒤 SQL을 고쳐 다시 적용하세요. 처음부터 다시 하고 싶다면make reset이 가장 빠릅니다.
- 마이그레이션이 중간에 실패하면
Go가 처음이라는 전제로 과제를 드립니다. 관용구가 어색한 것(에러 처리 반복, 패키지 배치, 네이밍 등)은 평가에 반영하지 않습니다. 보는 것은 설계 판단, 데이터 접근, 그리고 결정을 설명하는 방식입니다.
다만 아래 둘은 언어와 무관하게 봅니다.
- 에러를 삼키지 않을 것 (
_ = err로 넘기지 않기) - 동시성이 걸린 지점에서 무슨 일이 일어나는지 설명할 수 있을 것
make fmt와 make vet은 커밋 전에 한 번씩 돌려 주세요.
Go 1.26 / Gin / GORM / PostgreSQL 18 / golang-migrate / Docker Compose