JexLang: 한 번 작성, 어디서든 검증 — 플랫폼에 구애받지 않는 표현 언어
Source: Dev.to
🎯 우리 모두가 직면한 문제
이 상황을 상상해 보세요: React 웹 앱, Android 모바일 앱, Spring Boot 백엔드, 그리고 어쩌면 iOS 앱까지 포함한 멀티‑플랫폼 애플리케이션을 만들고 있습니다. 모든 것이 관리 가능한 것처럼 보이다가도 익숙한 장벽—검증 로직에 부딪히게 됩니다.
프로덕트 매니저가 들어와서 말합니다:
“우리는 사용자가 18세 이상인지, 이메일이 회사 도메인과 일치하는지, 그리고 프리미엄 플랜을 선택하면 유효한 신용카드가 있는지 검증해야 합니다.”
간단하죠? 이제 이를 여러 플랫폼에 걸쳐 곱해 보세요:
// JavaScript (Web/React Native/NodeJS)
if (
user.age >= 18 &&
user.email.endsWith('@company.com') &&
(user.plan !== 'Premium' || user.hasValidCard)
) {
// valid
}
// Java (Backend/Android)
if (
user.getAge() >= 18 &&
user.getEmail().endsWith("@company.com") &&
(!user.getPlan().equals("Premium") || user.hasValidCard())
) {
// valid
}
// Swift (iOS)
if user.age >= 18 &&
user.email.hasSuffix("@company.com") &&
(user.plan != "Premium" || user.hasValidCard) {
// valid
}
이제 50개 이상의 검증 규칙이 애플리케이션 전역에 있다고 상상해 보세요. 각 플랫폼마다 구현이 따로 있고, 각각 테스트가 필요하며, 미묘한 버그가 숨어 있을 수 있습니다.
검증 지옥에 오신 것을 환영합니다. 🔥
😫 실제 고통 포인트
1. 코드 중복 악몽
각 검증 규칙이 여러 곳에 존재합니다. 비즈니스 규칙이 바뀔 때마다 3‑4개의 다른 레포지토리에서 코드를 업데이트해야 합니다. 하나라도 놓치면 디버깅 세션을 괴롭히는 일관성 없는 동작이 발생합니다.
2. 미묘한 플랫폼 차이
각 언어는 약간씩 다르게 동작합니다:
- 문자열 비교:
===vs.equals()vs== - Null 처리:
undefinedvsnullvsOptional<> - 타입 강제 변환: JavaScript의 특이한
"5" + 3 = "53"vs Java의 엄격한 타입
이 차이점 때문에 특정 플랫폼에서만 나타나는 버그가 생깁니다—가장 골치 아픈 버그 유형이죠.
3. 하드코딩 로직 = 배포 고통
검증 규칙을 바꾸려면 코드 변경, PR 리뷰, 테스트, 스테이징, 배포—플랫폼마다 모두 진행해야 합니다. 검증 로직을 데이터베이스나 설정에 두면 어떨까요? 비개발자도 코드를 건드리지 않고 간단한 규칙을 수정할 수 있다면요?
4. 테스트 오버헤드
50개의 검증 규칙 × 4개 플랫폼 = 최소 200개의 테스트 케이스. 그리고 모두가 동일하게 동작하도록 보장해야 합니다. 행운을 빕니다.
💡 더 나은 방법이 있다면?
검증 로직을 한 번만 작성하고, 간단하고 익숙한 문법으로 표현한다면 어떨까요:
user.age >= 18 && endsWith(user.email, "@company.com") && (user.plan != "Premium" || user.hasValidCard)
…그리고 다음 환경에서도 동일하게 실행됩니다:
- ✅ JavaScript/TypeScript (브라우저 & Node.js)
- ✅ Java (Android & Backend)
- 🚧 Swift (iOS) — 곧 지원!
플랫폼별 특이점이 없습니다. 코드 중복도 없습니다. 배포 오버헤드도 없습니다.
바로 이것이 제가 JexLang을 만든 이유입니다.
🚀 JexLang 소개
JexLang은 JavaScript와 같은 문법을 갖춘 가볍고 플랫폼에 구애받지 않는 표현식 언어입니다. 한 가지 문제를 탁월하게 해결합니다: 로직을 한 번 작성하고, 어디서든 일관된 동작으로 실행.
왜 JavaScript‑같은 문법인가?
대부분의 개발자는 JavaScript 문법에 익숙합니다. 비록 주로 Java나 Swift를 사용하더라도 어느 순간 JavaScript을 접해본 적이 있을 겁니다. 익숙한 문법을 사용함으로써 JexLang은 사실상 학습 곡선이 거의 없습니다.
user.age >= 18 && user.active === true
이 코드를 쓸 수 있다면 이미 JexLang을 알고 있는 겁니다.
🛠️ JexLang 작동 방식
JexLang은 세 가지 핵심 구성 요소로 이루어집니다:
1. Lexer (Tokenizer)
표현식을 토큰으로 분해합니다:
"user.age >= 18" → [IDENTIFIER:user, DOT, IDENTIFIER:age, GTE, NUMBER:18]
2. Parser
토큰을 추상 구문 트리(AST)로 변환합니다:
{
"type": "BinaryExpression",
"left": {
"type": "MemberExpression",
"object": "user",
"property": "age"
},
"operator": ">=",
"right": {
"type": "NumericLiteral",
"value": 18
}
}
3. Interpreter
AST를 컨텍스트 데이터와 대조해 평가하고 결과를 반환합니다.
핵심은? 동일한 AST 구조와 평가 로직이 각 언어마다 동일하게 구현된다는 점—동일한 파서 규칙, 동일한 연산자 우선순위, 동일한 타입 강제 변환, 동일한 결과.
📖 실제 활용 사례
1. 동적 폼 검증
검증 규칙을 백엔드에 저장하고 모든 플랫폼에서 공유합니다:
{
"fields": [
{
"name": "email",
"validations": [
{ "rule": "required(email)", "message": "Email is required" },
{ "rule": "contains(email, '@')", "message": "Please enter a valid email" }
]
},
{
"name": "age",
"validations": [
{ "rule": "age >= 18", "message": "You must be 18 or older" }
]
}
]
}
React 앱, Android 앱, 백엔드가 이 규칙들을 받아 JexLang으로 평가합니다. 단일 진실 원천이 됩니다.
2. 비즈니스 규칙 엔진
코드 배포 없이 비즈니스 로직을 구성합니다:
// 데이터베이스에 저장된 규칙
const discountRules = [
{ condition: "customer.tier === 'Gold' && cart.total > 100", discount: 20 },
{ condition: "customer.tier === 'Silver' && cart.total > 200", discount: 10 },
{ condition: "cart.items.length > 5", discount: 5 }
];
// 런타임에 평가
const applicableRule = discountRules.find(rule =>
jexlang.evaluate(rule.condition, { customer, cart })
);
마케팅 팀이 골드 티어 기준을 100에서 150으로 바꾸고 싶다면? 데이터베이스만 업데이트하면 됩니다—배포가 필요 없습니다.
3. 조건부 UI 렌더링
구성 가능한 규칙에 따라 UI 요소를 표시/숨김합니다:
{
"component": "PremiumBanner",
"showWhen": "user.subscription === 'free' && user.daysActive > 30"
}
4. 워크플로 자동화
조건을 사용자가 직접 설정할 수 있는 워크플로 엔진을 구축합니다:
IF order.total > 1000 AND customer.verified === true
→ 우선 순위 처리로 라우팅
IF order.items.length > 10
→ 대량 처리 적용
IF contains(order.shippingAddress.country, 'EU')
→ VAT 계산 적용
5. 복잡한 로직을 가진 피처 플래그
단순 불리언 플래그를 넘어서는 기능 플래그:
const featureRules = {
newCheckout: "user.region === 'US' && user.accountAge > 30 && !user.hasActiveIssues",
betaFeatures: "user.role === 'developer' || contains(user.email, '@internal.com')"
};
⚡ 빠른 예제
기본 표현식
// 산술
jexlang.evaluate("10 + 5 * 2"); // 20
// 비교
jexlang.evaluate("100 >= 50"); // true
// 논리 연산자
jexlang.evaluate("true && false || true"); // true
// 문자열 연산
jexlang.evaluate("'Hello' + ' ' + 'World'"); // "Hello World"
컨텍스트와 함께 사용하기
const context = {
user: {
name: "John Doe",
age: 28,
email: "john@company.com",
roles: ["admin", "editor"]
},
settings: {
maxUploadSize: 100,
allowedFormats: ["jpg", "png", "pdf"]
}
};
// 중첩 속성 접근
jexlang.evaluate("user.name", context); // "John Doe"
jexlang.evaluate("settings.maxUploadSize > 50", context); // true