안녕하세요, 카카오뱅크 뱅킹서버팀의 서버 개발자 Ain입니다.
현재는 모임통장 서비스를 개발하고 있으며, 이전에는 대출·신용 등 여신 상품 개발을 담당했습니다. 당시 여신 업무의 복잡한 대출 심사 비즈니스 로직을 구현하는 과정에서 다음과 같은 어려움들을 경험했습니다.
- ✔️ 로직이 길어지고 단계가 많아질수록 가독성과 유지보수성은 저하되고, 정책 변경 시 특정 단계만 교체하기도 어려워진다. 😅
- ✔️ 프로젝트의 규모가 커질수록 테스트가 어려운 거대한 함수·서비스도 자주 등장한다. 😵
이 글에서는 이러한 문제를 해결하기 위해 고안한 파이프라인(Pipeline) 구조를 소개하려고 합니다. 이 구조 자체는 완전히 새로운 개념이라기보다는, 이미 여러 분야에서 활용되고 있어 익숙하게 느끼실 분들도 많을 것 같습니다. 그럼에도 불구하고 ‘금융’처럼 도메인의 특수성이 강하고 비즈니스 로직이 복잡한 환경에서 시스템을 개발하거나, 다양한 데이터를 가공·처리하는 시스템을 설계하고 운영하는 과정에서는 충분히 고민해볼 만한 아키텍처라고 생각합니다. 이러한 맥락에서 제 경험을 바탕으로 대출 심사 비즈니스 로직에 파이프라인 구조를 적용해 본 이야기를 소개하고자 합니다.
이 글이 장기적인 관점에서 시스템을 보다 효율적으로 개발하고, 코드의 구조를 정리해 유지보수성을 높이는 방법을 고민하고 계신 분들께 작은 참고 자료가 되길 바라며 시작해보겠습니다.
시작 배경: 공공 마이데이터가 쏘아올린 작은 공
여신 상품 개발자로 일하고 있던 어느 날, 저는 공공 마이데이터를 활용해 고객 문서를 수집하여 이를 카카오뱅크의 대출 심사에 필요한 형태로 가공하는 새로운 업무를 맡게 되었습니다.
💡 공공 마이데이터란?
공공 마이데이터는 공공·행정기관이 보유한 개인 행정 정보를 정보주체인 국민이 본인 또는 지정한 제3자에게 직접 전송할 수 있도록 한 데이터입니다. 예를 들어, 대출 심사를 위해 건강보험공단의 자격득실확인서나 납부확인서를 전송받아 소득, 경력, 납부 이력 등 다양한 정보를 사용자의 동의를 받아 확보할 수 있습니다.
문제는 필요한 데이터의 종류가 많을 뿐 아니라 가공 규칙 또한 상당히 복잡했다는 점이었습니다. 문서 자체는 두 가지뿐이었지만, 자격득실 정보에서 현직장·전직장·겸직 여부를 추출하고, 이를 납부확인서와 매칭해 월별 소득을 산출하며, 각종 조건에 따라 예외 처리를 하는 등 수많은 규칙이 얽혀 있었습니다. 이러한 로직이 한 함수에 누적되면서 어느새 코드가 500줄 가까이 불어나 있었고, 이 상태로는 유지보수는 물론 새로운 기능을 추가하는 것조차 쉽지 않을 것이 분명했습니다.
결국 전체 구조를 처음부터 다시 설계하기로 결정했습니다.
기존 서비스 코드의 한계
기존 로직은 현·전·겸직장 정보 추출, 월별 소득 합산, 정책별 예외 처리 등 여러 가공 단계를 하나의 함수에 계속 덧붙이는 방식이었습니다. 처음에는 하나의 큰 함수 안에서 여러 private 함수를 조합해 구현했지만, 곧 한계가 드러났습니다.
로직이 한 덩어리로 묶여 있다 보니 중간 단계의 상태를 확인하기 어렵고, 단위 테스트를 설계하는 것 자체가 쉽지 않았습니다. 결국 테스트를 작성하더라도 매번 전체 함수를 실행해 최종 결과만 검증하는 방식에 머물 수밖에 없었습니다.
// 기존 코드 예시 (모든 로직이 하나의 함수에 집중)
fun analyze(qualificationDocument, paymentDocument): AnalyzedDocument {
validateDocument(qualificationDocument, paymentDocument)
val currentCompany = extractCurrentCompany(qualificationDocument)
val previousCompany = extractPreviousCompany(qualificationDocument)
val currentCompanyPayment = extractCurrentCompanyPayment(currentCompany, paymentDocument)
val previousPayment = extractPreviousPayment(previousCompany, paymentDocument)
...
// … 새로운 정책이 추가될 때마다 이 아래로 코드가 무한히 길어진다.
// … 최종 결과로만 테스트가 가능하다.
return AnalyzedDocument(...)
}
이처럼 가공 로직이 계속 쌓일수록 코드는 점점 다시 열어보기도 싫은 덩어리가 되어 갔습니다. 결국 이 문제를 해결하기 위해 구조를 전면적으로 재설계하기로 했고, 그 과정에서 다음과 같은 세 가지 원칙을 세웠습니다.
2. 교체 가능한 단계: 정책이 바뀌면 해당 단계(Job)만 다른 구현으로 교체할 수 있어야 한다.
3. 테스트 친화성: 각 Job을 독립적으로 검증하고, 필요한 경우 중간 결과를 쉽게 확인할 수 있어야 한다.
이 기준에 가장 잘 맞았던 것은 사내에서 이미 활용하고 있던 GitLab CI/CD 파이프라인 구조였습니다.
💡 GitLab CI/CD 파이프라인이란?
소프트웨어의 빌드, 테스트, 배포 과정을 자동화하는 도구입니다. 복잡한 배포 프로세스를 ‘Build’, ‘Test’, ‘Deploy’처럼 여러 단계(Stage)로 나누고, 각 단계는 독립적인 작업(Job)들로 구성됩니다. 이 Job들이 순차적으로 실행되며 전체 과정이 완성되는 구조가 마치 공장의 ‘파이프라인’과 같다는 데서 이름이 유래했습니다.
복잡한 빌드 과정을 Job 단위로 분해해 순서대로 처리하듯, 비즈니스 로직도 Job 리스트로 구성하면 순서 변경, 특정 Job 교체, 버전 관리가 훨씬 간단해질 것이라고 판단했습니다. 이 아이디어를 바탕으로 GitLab 파이프라인 개념을 비즈니스 로직 아키텍처에 적용하기 시작했습니다.
파이프라인 구조의 주요 요소 3가지
파이프라인 아키텍처는 크게 Pipeline, Job, Store 세 가지 핵심 요소로 이루어져 있습니다. 이 구조를 하나의 작업 현장으로 비유해보면, Pipeline은 전체 공정을 설계하고 감독하는 관리자이고, Job은 각자 명확한 역할을 맡아 움직이는 작업자이며, Store는 작업자들이 결과물을 올려두고 다음 작업으로 넘기는 공용 작업대라고 볼 수 있습니다. 각 구성 요소는 자신의 책임에만 집중하며, 서로를 직접 의존하지 않고 정해진 규칙과 흐름 속에서 유기적으로 협업합니다.
요소 1. Pipeline: 전체 공정의 관리자
Pipeline 클래스는 파이프라인 전체를 하나의 공정으로 보고, 그 흐름을 조율하는 관리자 역할을 합니다. 개별 Job들이 각자 맡은 일을 수행하는 작업자라면, Pipeline은 이 작업자들이 언제, 어떤 순서로, 어떻게 움직일지를 결정하는 존재입니다. 실제 작업 현장을 떠올려 보면, 여러 작업자가 동시에 일하더라도 작업의 시작과 끝, 그리고 공정의 순서는 미리 정해져 있어야 혼란이 생기지 않습니다. Pipeline은 이러한 원칙을 코드로 명확하게 드러내기 위해 initJob(준비) → jobs(실행) → finishJob(마무리)라는 고정된 실행 흐름을 갖도록 설계되었습니다. 덕분에 코드를 위에서 아래로 읽기만 해도 전체 작업 과정이 자연스럽게 이해됩니다.
또한, Pipeline의 역할은 단순히 Job을 나열하는 데 그치지 않습니다. 어떤 Job이 어떤 순서로 실행되어야 하는지 정의하고, 각 Job이 정상적으로 수행되도록 실행을 책임지는 것이 핵심입니다. 반대로 개별 Job은 실행 순서나 전체 흐름에 대해 알 필요가 없습니다. 오직 Pipeline이 호출해줄 때 자신의 역할만 수행하면 됩니다.
이러한 구조를 통해 전체 로직의 흐름은 Pipeline 한 곳에 모이고, 세부적인 비즈니스 로직은 각 Job으로 분산됩니다. 그 결과 파이프라인의 구조는 명확해지고, 새로운 공정을 추가하거나 기존 흐름을 조정해야 할 때도 Pipeline만 수정하면 되기 때문에 시스템 전반의 변경 비용을 효과적으로 줄일 수 있습니다.
// 범용성을 위해 Store와 최종 결과 객체는 제네릭 타입으로 정의
class Pipeline<S, O>(
private val store: S,
private val initJob: Job<S>,
private val jobs: List<Job<S>>,
private val finishJob: FinishJob<S, O>,
) {
fun execute(): O {
// initJob부터 finishJob까지 순차적으로 실행
initJob.process(store)
jobs.forEach { it.process(store) }
// finishJob에서 최종 결과 반환
return finishJob.process(store)
}
}
요소 2. Job: 단일 책임을 갖는 작업자
Job은 파이프라인을 구성하는 가장 작은 작업 단위로, 각각이 명확한 역할을 가진 ‘작업자(Worker)’ 라고 볼 수 있습니다. 실제 작업 현장에서 한 사람이 여러 공정을 동시에 담당하지 않듯, Job 역시 “하나의 Job은 하나의 책임만 가진다”는 원칙 아래 설계됩니다.
예를 들어 어떤 Job은 문서의 유효성을 검증하는 역할만 수행하고, 또 다른 Job은 문서에서 현직장 정보를 추출하는 일만 담당합니다. 각 Job은 자신에게 주어진 입력을 받아 정해진 로직을 수행한 뒤, 결과를 Store에 기록하는 것까지만 책임집니다. 그 이후의 처리는 다음 Job의 몫입니다.
이처럼 역할이 명확히 분리되어 있으면, 특정 로직에 변경이 필요할 때도 해당 Job만 수정하면 되기 때문에 영향 범위를 최소화할 수 있습니다. 또한 Job을 인터페이스 기반으로 설계하면, 기존 Job을 새로운 구현체로 교체하거나, 새로운 Job을 파이프라인에 추가하는 작업도 자연스럽게 이루어집니다. 이는 마치 작업자 한 명을 다른 사람으로 교체하거나, 새로운 작업자를 팀에 투입하는 것과 같은 느낌으로 이해할 수 있습니다.
결과적으로 Job은 파이프라인 전체를 유연하고 확장 가능하게 만드는 핵심 구성 요소이며, 복잡한 비즈니스 로직을 작은 단위로 나누어 관리할 수 있게 해주는 역할을 합니다. 저의 경우, 다음과 같이 3가지 종류의 Job을 구성해보았습니다.
- •
InitJob: 파이프라인의 첫 단계로, 외부 시스템에서 데이터를 가져오거나 초기 검증을 수행합니다. 가공에 필요한 기초 데이터를 Store에 저장하는 역할을 합니다. - •
Job: 핵심 가공 단계를 담당합니다. Store에서 필요한 데이터를 읽어 가공하고, 그 결과를 다시 Store에 반영합니다. - •
FinishJob: 모든 가공이 끝난 데이터를 최종 응답 형태(DTO 등)로 변환해 반환하며, 전체 파이프라인의 마무리를 담당합니다.
class InitJob(
...
private val originDocument: NhisDocument,
) : Job<Store> {
override fun process(store: Store) {
...
// 원본 문서 데이터 setting
}
}
// 모든 Job들이 구현해야 하는 공통 인터페이스
interface Job<S> {
fun process(store: S)
}
// 최종 결과를 반환하는 특별한 Job 인터페이스
interface FinishJob<S, O> {
fun process(store: S): O
}
요소 3. Store: Job 간의 데이터 공유를 위한 작업대
Store는 파이프라인에서 여러 Job이 함께 사용하는 ‘공용 작업대(Workspace)’ 에 해당합니다. 실제 작업 현장을 떠올려 보면, 각 작업자는 자기 일을 마친 뒤 결과물을 작업대 위에 올려두고, 다음 작업자는 그 결과물을 집어 들어 자신의 작업을 이어갑니다. Store는 바로 이 역할을 수행합니다.
예를 들어 A Job이 문서에서 원천 데이터를 추출했다면, 그 결과는 Store에 저장됩니다. 이후 B Job은 Store에서 해당 데이터를 가져와 정제하거나 검증하는 작업을 수행합니다. 이 과정에서 Job들은 서로를 직접 참조하지 않고, 오직 Store를 통해서만 데이터를 주고받습니다. 덕분에 Job 간의 결합도가 낮아지고, 각 Job은 자신의 책임에만 집중할 수 있습니다.
또한 Store를 명시적인 필드를 가진 data class로 설계하면, 파이프라인 전반에서 어떤 데이터가 언제, 어떤 형태로 공유되는지를 한눈에 파악할 수 있습니다. 이는 코드를 읽는 사람에게도 큰 장점이며, 테스트 환경에서도 특정 단계의 상태를 Store에 직접 세팅해 원하는 시나리오를 쉽게 재현할 수 있게 해줍니다. 즉, Store는 단순한 데이터 컨테이너를 넘어 파이프라인의 흐름을 가시화해주는 핵심 구성 요소라고 볼 수 있습니다.
data class Store(
var originDocument: NhisDocument = NhisDocument.EMPTY,
var currentCompany: String = "",
var previousCompany: String = "",
var currentCompanyPayment: List<Long>? = null,
var previousCompanyPayment: List<Long>? = null,
// ... 필요한 중간 결과 필드들
)
🔍 파이프라인 구조 한눈에 보기
위 세 가지 주요 요소를 반영한 전체 구조는 다음과 같습니다.
이러한 구조는 금융 도메인처럼 비즈니스 규칙이 복잡하고, 단계별 처리가 명확하게 구분되어야 하는 환경과 특히 잘 어울립니다. 대출 심사나 자격 검증과 같은 프로세스에서는 데이터가 어떤 단계를 거쳐 어떻게 변형되었는지 추적 가능해야 하고, 특정 단계의 로직만 수정하거나 교체해야 하는 상황도 빈번하게 발생합니다. 파이프라인 구조는 이러한 요구사항을 자연스럽게 만족시키며, 전체 흐름의 안정성을 유지한 채 부분적인 변경을 가능하게 합니다.
결과적으로 이 구조는 복잡한 로직을 작은 단위로 나누어 이해하기 쉽게 만들고, 데이터 흐름을 명확하게 드러내며, 장기적인 관점에서 시스템의 유지보수성과 확장성을 높이는 데 기여합니다. 금융은 변화에 민감하면서도 높은 신뢰성이 요구되는 도메인이기에 파이프라인 구조를 시스템에 적용한다면 유용하게 사용할 수 있을 것이라 생각했습니다.
실제 사용 예시: 정책 변경에 유연하게 대처하기
금융 도메인에서는 변화하는 금융 정책에 기민하게 대응해야 할 때가 많으며, 이러한 변화는 기존 로직에 적지 않은 영향을 미칩니다. 예를 들어, 최근 다음과 같은 새로운 정책 요구사항이 추가된 상황을 가정해 보겠습니다.
이 요구사항은 ‘현직장 판단 로직’에 직접적인 영향을 미칩니다. 기존 구현이 하나의 거대한 함수에 여러 판단 로직이 얽혀 있는 구조였다면, 상황은 꽤 까다로워집니다. 기존의 거대한 함수 구조였다면 어땠을까요? extractCurrentCompany 함수 내부를 수정해야 하고, 그 결과를 사용하는 extractCurrentCompanyPayment를 비롯한 다른 로직들까지 영향을 받지는 않는지 전체 흐름을 다시 따라가며 확인해야 합니다.
본 코드는 설명의 편의를 위해 실제 비즈니스 로직을 단순화(V2)하여 시뮬레이션한 예시입니다. 특정 모듈의 교체 과정을 직관적으로 보여드리기 위해 명칭을 임의로 조정하였습니다.
// 기존의 현직장 추출 Job
class ExtractCurrentCompany : Job<Store> {
override fun process(store: Store) {
// 직장가입자 정보만 추출
if (store.originDocument.status == "직장가입자" && store.originDocument.status == "재직중") {
store.currentCompany = store.originDocument.company
}
}
}
// 새로운 정책이 반영된 Job
class ExtractCurrentCompanyV2 : Job<Store> {
override fun process(store: Store) {
// 직장가입자 + 지역가입자 정보 추출
if (store.originDocument.status == "직장가입자" && store.originDocument.status == "재직중") {
store.currentCompany = store.originDocument.company
} else if(store.originDocument.status == "지역가입자") {
store.currentCompany = store.originDocument.company
}
}
}
class NhisAnalyzeService {
fun analyze(originDocument: NhisDocument): AnalyzedNhisDocument {
val pipeline = Pipeline(
store = Store(),
initJob = InitJob(originDocument),
jobs = listOf(
ValidateDocument(),
ExtractCurrentCompanyV2(), // <-- V1에서 V2로 이 Job만 교체
ExtractPreviousCompany(),
ExtractCurrentCompanyPayment(),
ExtractPreviousCompanyPayment(),
...
),
finishJob = GenerateAnalyzedNhisDocument()
)
return pipeline.execute()
}
}
하지만 파이프라인 구조에서는 아주 간단하게 해결할 수 있습니다. ‘현직장 정보를 추출한다’는 책임은 이미 하나의 Job, 즉 ExtractCurrentCompany에 명확하게 분리되어 있기 때문입니다. 새로운 정책을 반영한 로직은 기존 Job을 수정하는 대신, 이를 대체하는 새로운 Job으로 구현합니다.
따라서 정책 변경 이후에는 ExtractCurrentCompany를 새로운 요구사항을 반영한 ExtractCurrentCompanyV2로 교체하기만 하면 되고, 다른 Job들은 store.currentCompany를 사용해 영향을 받는 곳만 수정하면 됩니다. 덕분에 변경의 영향 범위는 자연스럽게 한정되고, 테스트 역시 영향을 받는 부분에 대해서만 집중적으로 작성하면 됩니다. 결과적으로 테스트의 부담도 크게 줄어듭니다.
// Job 단위의 테스트는 무엇을 검증할지 명확해진다.
class ExtractCurrentCompanyV2Test : FunSpec({
test("퇴사 후 개인사업자 자격이 있다면, 현직장으로 포함해야 한다.") {
// given
val store = Store(originDocument = createDocumentWithLocal())
// when
ExtractCurrentCompanyV2().process(store)
// then
store.currentCompany shouldContain "지역가입자"
}
})
이 예시는 파이프라인 구조가 정책 변화에 어떻게 대응하는지를 잘 보여줍니다. 전체 흐름을 건드리지 않고도 특정 단계의 로직을 안전하게 교체할 수 있고, 변경의 이유와 위치가 코드 구조상 명확하게 드러납니다. 정책 변화가 잦고, 판단 기준이 계속 진화하는 금융 도메인에서 이러한 구조는 개발자의 부담을 줄이고, 시스템의 안정성을 유지하는 데 큰 도움을 준다고 할 수 있습니다.
파이프라인 구조 적용 후 얻게 된 것들
파이프라인 구조를 도입한 이후, 우리는 단순히 코드의 형태만 바꾼 것이 아니라 시스템을 바라보는 관점과 일하는 방식 전반에서 의미 있는 변화를 경험할 수 있었습니다. 이전에는 기능을 추가하거나 정책을 수정할 때마다 “어디까지 영향이 갈까”를 먼저 걱정해야 했다면, 이제는 “어떤 Job이 이 책임을 가져야 할까”를 고민하는 방식으로 사고가 전환되었습니다. 이러한 변화는 개발 과정 전반의 안정성과 예측 가능성을 크게 높여주었습니다.
무엇이 달라졌을까요?
가장 먼저 체감할 수 있었던 변화는 정책 변경에 대한 유연성이었습니다. 앞선 예시에서 살펴본 것처럼, 정책이 변경되더라도 기존 로직을 광범위하게 수정할 필요 없이 해당 책임을 가진 Job만 새로 구현하거나 교체하면 됩니다. 이로 인해 변경의 영향 범위가 자연스럽게 Job 단위로 한정되었고, 수정 후 발생할 수 있는 사이드 이펙트에 대한 부담도 크게 줄어들었습니다. 결과적으로 정책 변경에 대응하는 속도 역시 눈에 띄게 빨라졌습니다.
개인적으로도 개발에 대한 심리적 부담이 줄어든 점이 인상 깊었습니다. 이전에는 하나의 함수 안에 여러 판단 로직이 얽혀 있는 수백 줄의 코드를 마주하며, 작은 수정 하나가 예상치 못한 문제를 일으키지는 않을지 늘 경계해야 했습니다. 반면 파이프라인 구조에서는 새로운 요구사항이 생기더라도 “이 로직은 어떤 Job으로 분리할 수 있을까”를 먼저 떠올리게 되었고, 실제 구현 역시 Job 하나를 추가하거나 교체하는 수준에서 마무리되는 경우가 많았습니다. 이는 개발자의 집중력을 불필요하게 소모하지 않게 해주는 중요한 변화였습니다.
또한 동료 F의 피드백을 통해, 이 구조가 팀 차원에서도 긍정적인 영향을 미친다는 점을 확인할 수 있었습니다. 각 데이터 가공 단계의 관심사가 Job 단위로 명확하게 분리되어 있다 보니, 새로운 비즈니스 요구사항을 분석할 때도 “어디를 수정해야 하는지”, “어디를 테스트해야 하는지”가 비교적 빠르게 드러났습니다. 특히 모든 파이프라인을 처음부터 끝까지 실행하지 않더라도, 특정 Job 단계부터 실행해 결과를 확인할 수 있었기 때문에 개발과 검증을 훨씬 짧은 피드백 사이클로 진행할 수 있었습니다.
이처럼 파이프라인 구조는 코드의 가독성과 유지보수성을 개선하는 데 그치지 않고, 정책 변경이 잦고 복잡한 금융 도메인에서 개발자가 보다 안정적으로 문제에 집중할 수 있는 환경을 만들어주었습니다. 물론 아직 모든 문제가 해결된 것은 아니며, Job 간 의존성 관리나 파이프라인 구성의 복잡도가 커질수록 이를 어떻게 관리할 것인지는 앞으로 계속 고민해야 할 과제로 남아 있습니다. 그럼에도 불구하고, 지금까지의 경험을 돌아보면 파이프라인 구조는 이러한 고민을 감수할 만한 충분한 가치를 제공해주고 있다고 느끼고 있습니다.
마치며
아키텍처 관련 자료를 찾아보면 헥사고날 아키텍처나 DDD처럼 큰 틀의 개념과 원칙에 대한 이야기는 쉽게 접할 수 있습니다. 하지만 실제 현업에서 마주하는 문제, 특히 빠르게 변하는 정책과 복잡한 비즈니스 규칙을 코드로 어떻게 풀어내고 장기적으로 어떻게 유지보수할 것인지에 대한 구체적인 사례는 많지 않다는 생각을 했습니다. 실무에서는 이론적으로 훌륭해 보이는 구조라도 팀의 상황이나 도메인 특성과 맞지 않으면 부담이 되는 경우가 종종 있었기에, 결국 우리 팀과 비즈니스에 가장 적합한 구조를 찾는 데 집중했습니다.
제가 파이프라인 구조를 고민하게 된 이유도 거창한 아키텍처를 도입하고 싶어서가 아니라, 눈앞의 복잡한 로직을 조금이라도 더 안전하고 명확하게 다루고 싶었던 현실적인 필요에서 출발했습니다. 수백 줄의 코드 안에 얽힌 정책을 수정할 때마다 느꼈던 불안감과, 작은 변경 하나에도 전체 흐름을 다시 짚어야 했던 경험들이 이러한 시도의 원동력이었습니다.
물론 이 글에서 소개한 구조가 모든 상황의 정답이라고 생각하지는 않습니다. 이는 여러 시행착오 끝에 당시 우리 팀과 도메인에 비교적 잘 맞았던 하나의 선택일 뿐입니다. 다만 복잡한 비즈니스 로직을 작은 단위로 나누고, 처리 흐름을 구조적으로 드러내려 했던 고민의 과정 자체는 누군가에게 참고가 될 수 있는 가치 있는 경험이라고 생각했습니다.
이 글에서 전하고 싶었던 메시지는, 개발에는 단일한 정답이 존재하지 않으며 결국 중요한 것은 현재의 팀과 비즈니스 환경, 그리고 앞으로의 변화에 맞추어 구조를 꾸준히 조정해 나가는 일이라고 생각한다는 점입니다. 저 역시 앞으로도 정답을 찾기보다는 그때그때의 문제를 가장 적절하게 해결하기 위한 방법을 고민하며, 제가 맡은 시스템과 과제를 성실히 풀어가고자 합니다. 이 글이 비슷한 고민을 하고 계신 분들께 “이런 접근도 가능하구나” 하는 작은 출발점이 된다면 그 자체로 의미 있는 기록이라 생각합니다. 읽어주셔서 감사합니다.