자바 개발자의 딜레마: 1부
출처: The Java Developer's Dilemma: Part 1
마르쿠스 아이젤레(Markus Eisele)의 3부작 시리즈 중 첫 번째 글이다. 후속 글들을 기대하시라.
AI가 지금 모든 곳에 있다. 모든 컨퍼런스, 기조연설, 사내 회의마다 누군가가 대규모 언어 모델로 구동되는 프로토타입을 시연한다. 인상적으로 보인다. 질문을 하면 시스템이 자연어로 답변한다. 하지만 엔터프라이즈 자바 개발자라면 복잡한 심경일 것이다. 규모를 확장할 수 있고, 규정을 준수하며, 수년간 운영되는 안정적인 시스템을 구축하는 것이 얼마나 어려운지 잘 알고 있다. 데모에서 멋져 보이는 것이 프로덕션에서는 종종 무너진다는 것도 알고 있다. 이것이 우리가 직면한 딜레마다. 자바를 엔터프라이즈 소프트웨어의 표준으로 만든 품질들을 포기하지 않으면서 AI를 어떻게 이해하고 우리 세계에 적용할 것인가?
엔터프라이즈에서 자바의 역사
자바가 엔터프라이즈 시스템의 중추가 된 데는 이유가 있다. 강력한 타입 시스템, 메모리 안전성, 운영체제 간 이식성, 그리고 모범 사례를 체계화한 프레임워크 생태계를 제공했다. Jakarta EE, Spring, 또는 이후의 Quarkus와 Micronaut을 사용하든, 목표는 동일했다. 안정적이고 예측 가능하며 유지보수가 용이한 시스템을 구축하는 것이다. 엔터프라이즈들이 막대한 투자를 한 이유는 자바 애플리케이션이 수년 후에도 최소한의 예상치 못한 문제와 함께 여전히 실행될 것임을 알았기 때문이다.
이 역사는 AI를 논할 때 중요하다. 자바 개발자들은 결정론적(deterministic) 동작에 익숙하다. 메서드가 결과를 반환하면, 입력이 동일한 한 그 결과를 신뢰할 수 있다. 비즈니스 프로세스는 그러한 예측 가능성에 의존한다. AI는 그렇게 작동하지 않는다. 출력은 확률적이다. 동일한 입력이 다른 결과를 줄 수 있다. 그것만으로도 엔터프라이즈 소프트웨어에 대해 우리가 아는 모든 것에 도전한다.
프로토타입과 프로덕션 간의 격차
오늘날 대부분의 AI 작업은 프로토타입으로 시작한다. 팀이 API에 연결하고, 채팅 인터페이스를 구성하고, 결과를 시연한다. 프로토타입은 탐색에는 좋다. 프로덕션에는 좋지 않다. 규모에 맞춰 실행하려고 하면 문제가 발견된다.
지연(latency)이 한 가지 이슈다. 원격 모델 호출은 몇 초가 걸릴 수 있다. 2초 지연이 영원처럼 느껴지는 시스템에서는 받아들일 수 없다. 비용도 또 다른 이슈다. 호스팅된 모델 호출은 무료가 아니며, 수천 명의 사용자에 걸쳐 반복되는 호출은 빠르게 누적된다. 보안과 컴플라이언스는 훨씬 더 큰 우려사항이다. 엔터프라이즈는 데이터가 어디로 가는지, 어떻게 저장되는지, 공유 모델에 유출되는지 알아야 한다. 빠른 데모는 이러한 질문에 거의 답하지 못한다.
결과적으로 많은 프로토타입이 프로덕션에 진입하지 못한다. 데모와 프로덕션 시스템 사이의 격차는 크며, 대부분의 팀은 그것을 메우는 데 필요한 노력을 과소평가한다.
자바 개발자에게 이것이 중요한 이유
자바 개발자들은 종종 이러한 프로토타입을 받아서 "실제로 만들라"는 요청을 받는 사람들이다. 이는 해결되지 않은 모든 이슈들을 다루어야 함을 의미한다. 예측 불가능한 출력을 어떻게 처리할 것인가? AI 동작을 어떻게 로깅하고 모니터링할 것인가? 응답이 하위 시스템에 도달하기 전에 어떻게 검증할 것인가? 이것들은 사소한 질문이 아니다.
동시에 비즈니스 이해관계자들은 결과를 기대한다. 그들은 AI의 가능성을 보고 기존 플랫폼에 통합되기를 원한다. 제공해야 한다는 압박이 강하다. 딜레마는 우리가 AI를 무시할 수 없지만, 순진하게 채택할 수도 없다는 것이다. 우리의 책임은 실험과 프로덕션 사이의 격차를 메우는 것이다.
위험이 나타나는 지점
이것을 구체적으로 만들어보자. AI 기반 고객 지원 도구를 상상해보자. 프로토타입은 채팅 인터페이스를 호스팅된 LLM에 연결한다. 간단한 질문으로는 데모에서 작동한다. 이제 프로덕션에 배포된 것을 상상해보자. 고객이 계좌 잔액에 대해 묻는다. 모델이 환각(hallucinate)을 일으켜 숫자를 지어낸다. 시스템은 방금 컴플라이언스 규칙을 위반했다. 또는 사용자가 악의적인 입력을 제출하고 모델이 해로운 것으로 응답하는 것을 상상해보자. 갑자기 보안 사고에 직면하게 된다. 이것들은 "모델이 때때로 틀린다"를 넘어서는 실제 위험이다.
자바 개발자에게 이것이 딜레마다. 우리가 중요하다고 아는 품질들을 보존해야 한다. 정확성, 보안, 유지보수성이다. 그러나 우리가 익숙한 것과는 매우 다르게 동작하는 새로운 기술 클래스도 수용해야 한다.
자바 표준과 프레임워크의 역할
좋은 소식은 자바 생태계가 이미 도움을 주기 위해 움직이고 있다는 것이다. AI 통합을 덜 황량하게 만드는 표준과 프레임워크가 등장하고 있다. OpenAI API가 표준이 되어, 벤더에 관계없이 표준 형태로 모델에 접근하는 방법을 제공한다. 이는 오늘 작성하는 코드가 단일 제공자에 종속되지 않음을 의미한다. 모델 컨텍스트 프로토콜(Model Context Protocol, MCP)은 또 다른 단계로, 도구와 모델이 일관된 방식으로 상호작용할 수 있는 방법을 정의한다.
프레임워크도 진화하고 있다. Quarkus는 LangChain4j용 확장을 갖추고 있어, REST 엔드포인트를 정의하는 것만큼 쉽게 AI 서비스를 정의할 수 있다. Spring은 Spring AI를 도입했다. 이러한 프로젝트들은 의존성 주입, 구성 관리, 테스팅의 규율을 AI 영역으로 가져온다. 다시 말해, 낯선 문제에 대해 자바 개발자들에게 익숙한 도구를 제공한다.
표준 대 속도의 딜레마
자바와 엔터프라이즈 표준에 대한 일반적인 반론은 너무 느리게 움직인다는 것이다. AI 세계는 매달 변화하며, 어떤 표준 기구도 따라갈 수 없는 속도로 새로운 모델과 API가 나타난다. 얼핏 보면 표준이 진보의 장벽처럼 보인다. 현실은 다르다. 엔터프라이즈 소프트웨어에서 표준은 우리를 뒤로 잡아당기는 닻이 아니다. 장기적인 진보를 가능하게 하는 토대다.
표준은 공유된 어휘를 정의한다. 지식이 프로젝트와 팀 간에 전달 가능하도록 보장한다. JDBC를 아는 개발자를 고용하면, 드라이버 생태계가 지원하는 모든 데이터베이스와 작업할 수 있을 것으로 기대할 수 있다. Jakarta REST에 의존한다면, 모든 서비스를 다시 작성하지 않고도 프레임워크나 벤더를 교체할 수 있다. 이것은 느린 것이 아니다. 엔터프라이즈가 지속적으로 문제를 일으키지 않으면서 빠르게 움직일 수 있게 하는 것이다.
AI도 다르지 않을 것이다. 독점 API와 벤더 특화 SDK는 빠르게 시작하게 해줄 수 있지만, 숨겨진 비용이 따른다. 한 제공자에 종속되거나, 소수의 전문가만 이해하는 시스템을 구축할 위험이 있다. 그 사람들이 떠나거나 벤더가 조건을 변경하면 곤란해진다. 표준은 그 함정을 피한다. 오늘의 투자가 수년 후에도 유용하게 유지되도록 보장한다.
또 다른 장점은 지원 기간이다. 엔터프라이즈는 몇 주나 해커톤 데모 단위로 생각하지 않는다. 수년 단위로 생각한다. 표준 기구와 확립된 프레임워크는 장기간에 걸쳐 API와 사양을 지원하기로 약속한다. 그 안정성은 금융 거래를 처리하고, 의료 데이터를 관리하고, 공급망을 운영하는 애플리케이션에 필수적이다. 표준이 없으면 모든 시스템이 일회성이 되고, 취약하며 그것을 구축한 사람에게 의존하게 된다.
자바는 이것을 거듭 보여주었다. Servlets, CDI, JMS, JPA - 이러한 표준들은 수십 년간의 비즈니스 크리티컬 개발을 확보했다. 수백만 개발자가 핵심 인프라를 재발명하지 않고 애플리케이션을 구축할 수 있게 했다. 또한 벤더와 오픈소스 프로젝트가 종속성이 아닌 품질로 경쟁할 수 있게 만들었다. AI에도 마찬가지일 것이다. LangChain4j와 모델 컨텍스트 프로토콜용 Java SDK 또는 Agent2Agent 프로토콜 SDK 같은 신흥 노력들이 우리를 늦추지 않을 것이다. 엔터프라이즈가 안전하고 지속 가능하게 규모에 맞춰 AI를 채택할 수 있게 해줄 것이다.
결국 표준 없는 속도는 단명하는 프로토타입으로 이어진다. 속도를 가진 표준은 생존하고 진화하는 시스템으로 이어진다. 자바 개발자들은 표준을 제약으로 보아서는 안 된다. 실제로 중요한 프로덕션에 AI를 투입할 수 있게 하는 메커니즘으로 보아야 한다.
성능과 수치 계산: 자바의 따라잡기
딜레마의 또 다른 부분은 성능이다. 파이썬이 AI의 기본 언어가 된 것은 구문 때문이 아니라 라이브러리 때문이다. NumPy, SciPy, PyTorch, TensorFlow는 모두 고도로 최적화된 C와 C++ 코드에 의존한다. 파이썬은 대부분 이러한 수학 커널을 감싸는 프론트엔드 래퍼다. 반면 자바는 동일한 채택도나 깊이의 수치 계산 라이브러리를 가진 적이 없었다. JNI는 네이티브 코드 호출을 가능하게 했지만, 어색하고 안전하지 않았다.
이것이 변하고 있다. 외부 함수 및 메모리(Foreign Function & Memory, FFM) API(JEP 454)는 JNI의 보일러플레이트 없이 자바에서 네이티브 라이브러리를 직접 호출할 수 있게 한다. 더 안전하고, 더 빠르며, 사용하기 쉽다. 이는 자바 애플리케이션이 파이썬을 구동하는 것과 동일한 최적화된 수학 라이브러리와 통합할 수 있는 문을 연다. FFM과 함께 벡터(Vector) API(JEP 508)는 현대 CPU의 SIMD 연산에 대한 명시적 지원을 도입한다. 개발자가 하드웨어 플랫폼에 걸쳐 효율적으로 실행되는 벡터화된 알고리즘을 자바로 작성할 수 있게 한다. 이러한 기능들이 함께 자바를 AI와 머신러닝 워크로드에 필요한 성능 프로필에 훨씬 가깝게 만든다.
엔터프라이즈 아키텍트에게 이것이 중요한 이유는 AI 시스템에서 자바의 역할을 바꾸기 때문이다. 자바는 외부 서비스를 호출하는 오케스트레이션 레이어만이 아니다. Jlama 같은 프로젝트를 통해 모델이 JVM 내부에서 실행될 수 있다. FFM과 벡터 API로 자바는 네이티브 수학 라이브러리와 하드웨어 가속의 이점을 누릴 수 있다. 이는 AI 추론이 데이터 센터든 엣지든 데이터가 있는 곳에 더 가까이 이동할 수 있음을 의미하며, 동시에 자바 생태계의 표준과 규율로부터 여전히 혜택을 받는다.
테스팅 차원
딜레마의 또 다른 부분은 테스팅이다. 엔터프라이즈 시스템은 테스트될 때만 신뢰받는다. 자바는 모든 개발자가 아는 표준과 프레임워크가 지원하는 단위 테스트와 통합 테스트의 오랜 전통을 가지고 있다. JUnit, TestNG, Testcontainers, Jakarta EE 테스팅 하네스, 그리고 최근에는 통합 테스트에서 의존성을 가동하기 위한 Quarkus Dev Services 등이다. 이러한 관행은 자바 애플리케이션이 프로덕션 수준으로 간주되는 핵심 이유다. 하멜 후세인(Hamel Husain)의 평가 프레임워크 작업이 여기서 직접적으로 관련이 있다. 그는 세 가지 수준의 평가를 설명한다. 단위 테스트, 모델/인간 평가, 그리고 프로덕션 대면 A/B 테스트다. 모델을 블랙박스로 취급하는 자바 개발자에게 처음 두 수준은 기존 관행에 깔끔하게 매핑된다. 결정론적 컴포넌트를 위한 단위 테스트와 시스템 동작을 위한 선별된 프롬프트를 사용한 블랙박스 평가다.
AI가 주입된 애플리케이션은 새로운 도전을 가져온다. 매번 약간 다른 답변을 주는 모델에 대해 단위 테스트를 어떻게 작성할 것인가? AI 컴포넌트가 올바르게 작동하는지 어떻게 검증할 것인가? "올바름"의 정의가 모호할 때 말이다. 답은 테스팅을 포기하는 것이 아니라 확장하는 것이다.
단위 수준에서는 AI 서비스 주변의 결정론적 컴포넌트를 여전히 테스트한다. 컨텍스트 빌더, 데이터 검색 파이프라인, 검증, 가드레일 로직이다. 이것들은 고전적인 단위 테스트 대상으로 남는다. AI 서비스 자체에 대해서는 스키마 검증 테스트, 골든 데이터셋, 경계가 있는 단언(assertion)을 사용할 수 있다. 예를 들어, 모델이 유효한 JSON을 반환하고, 필수 필드를 포함하며, 허용 가능한 범위 내의 결과를 생성한다고 단언할 수 있다. 정확한 단어는 다를 수 있지만, 구조와 경계는 유지되어야 한다.
통합 수준에서는 AI를 그림에 포함시킬 수 있다. Dev Services는 반복 가능한 테스트 실행을 위해 로컬 Ollama 컨테이너나 모의 추론 API를 가동할 수 있다. Testcontainers는 pgvector를 사용하는 PostgreSQL이나 Elasticsearch 같은 벡터 데이터베이스를 관리할 수 있다. jqwik 같은 속성 기반 테스팅 라이브러리는 AI 파이프라인의 엣지 케이스를 노출시키기 위해 다양한 입력을 생성할 수 있다. 이러한 도구들은 이미 자바 개발자들에게 익숙하다. 단지 새로운 대상에 적용하면 된다.
핵심 통찰은 AI 테스팅이 우리가 이미 가지고 있는 테스팅 규율을 보완해야 한다는 것이지, 대체하는 것이 아니다. 엔터프라이즈는 테스트되지 않은 AI를 프로덕션에 투입하고 최선을 바랄 수 없다. AI가 주입된 컴포넌트에 단위 및 통합 테스팅 관행을 확장함으로써, 이해관계자들에게 이러한 시스템이 정의된 경계 내에서 동작한다는 확신을 준다. 개별 모델 출력이 확률적일지라도 말이다.
이것이 자바의 테스팅 문화가 장점이 되는 지점이다. 팀들은 배포 전에 포괄적인 테스트 커버리지를 이미 기대한다. 그 사고방식을 AI로 확장하면 이러한 애플리케이션이 데모 요구사항이 아닌 엔터프라이즈 표준을 충족하도록 보장한다. 시간이 지나면서 AI 출력에 대한 테스팅 패턴은 JUnit이 단위 테스트에, Arquillian이 통합 테스트에 가져온 것과 동일한 종류의 사실상 표준으로 성숙해질 것이다. AI가 주입된 애플리케이션을 위한 평가 프레임워크가 엔터프라이즈 스택에서 JUnit만큼 정상적인 것이 될 것으로 기대해야 한다.
나아갈 길
그렇다면 무엇을 해야 할까? 첫 번째 단계는 AI가 사라지지 않을 것임을 인정하는 것이다. 엔터프라이즈가 그것을 요구할 것이고, 고객들이 그것을 기대할 것이다. 두 번째 단계는 현실적이 되는 것이다. 모든 프로토타입이 제품이 될 자격이 있는 것은 아니다. 유스케이스를 신중하게 평가하고, AI가 진정한 가치를 더하는지 묻고, 위험을 염두에 두고 설계해야 한다.
거기서부터 나아갈 길은 익숙해 보인다. 종속을 피하기 위해 표준을 사용하라. 복잡성을 관리하기 위해 프레임워크를 사용하라. 트랜잭션, 메시징, 관찰 가능성에 이미 사용하는 것과 동일한 규율을 적용하라. 차이점은 이제 확률적 동작도 처리해야 한다는 것이다. 이는 검증 레이어를 추가하고, AI 출력을 모니터링하고, 모델이 틀렸을 때 우아하게 실패하는 시스템을 설계하는 것을 의미한다.
자바 개발자의 딜레마는 AI를 사용할지 여부를 선택하는 것이 아니다. 그것을 어떻게 책임감 있게 사용할 것인가다. AI를 애플리케이션에 넣고 잊어버리는 라이브러리처럼 취급할 수 없다. 중요한 시스템에 적용하는 것과 동일한 주의를 가지고 통합해야 한다. 자바 생태계는 그것을 할 수 있는 도구를 제공하고 있다. 우리의 도전은 빠르게 배우고, 그 도구들을 적용하고, 애초에 자바를 엔터프라이즈 표준으로 만든 품질들을 유지하는 것이다.
이것은 더 큰 대화의 시작이다. 다음 글에서는 AI가 애드온이 아니라 아키텍처의 핵심 부분으로 취급될 때 등장하는 새로운 유형의 애플리케이션을 살펴볼 것이다. 진정한 변화는 바로 거기서 일어난다.
