Shared logic belongs in one layer, never duplicated per surface.
Unified PC/mobile validation into one shared function reading fresh DB state; cross-surface mismatch bugs (~20% of changes) went to zero.
- Validation lived in UI event handlers, read grid cells
- PC/mobile couldn't share — nothing to share
- ~20% of feature changes shipped cross-surface mismatch
- Fix: one shared function, read fresh DB state
- My boilerplate + specialist's rules; phased rollout
- Result: that bug class to zero; identical results
// Before: logic trapped in the desktop UI handler
grid.addCellChangeListener(e -> {
if (grid.cell("qty") > grid.cell("max")) // stale UI state
block();
});
// After: one entry point, fresh DB, both surfaces call it
ValidationResult validate(LotId id) {
Lot row = db.getLot(id); // fresh state, not the UI
return rules.check(row); // the specialist's business rules
}
// PC and mobile: same signature, same input, same output- ·Validation logic wasn't in the business layer — it was inside the UI screens.
- ·There was nothing to share; the logic only lived in the PC UI.
- ·Highest standards meant fixing the root, not the symptom.
- ·My boilerplate plus his rules — each did what we knew best.
- ·I'd be first name on the post-mortem. I accepted that.
- Opendoor: validation in one shared layer reading fresh state, not copied per client.
- Proves: single-source correctness, no duplicated rules.
- Use it when client-vs-server or duplicated validation comes up.
- A shared library still forces every consumer to redeploy on a rule change.
- Reading fresh DB on each validate adds a round trip and read load.
- No client pre-check means worse latency and no offline validation.
- A buggy rule ships to all surfaces at once via the shared lib.
- Promote to a validation service so rules update without consumer redeploys.
- Cache hot lot state with short TTL to cut DB round trips.
- Add contract tests so each surface verifies the shared signature.
- Version rules and roll out behind flags to bound blast radius.
Understand it step by step (한국어로 이해 → 영어로 말하기)
- 1
검사 규칙이 화면(UI) 코드 안에 들어가 있었다. PC 화면의 표(grid) 칸 값을 직접 읽어서 '이게 맞나' 판단했다.
🔧 도구:UI event handler (grid.CellChanged)
// Correct: the UI handler stays thin and delegates to the shared layer grid.addCellChangeListener(e -> { ValidationResult result = validate(lotId); // logic lives outside the UI if (!result.ok()) block(); });🗣 영어로 말해“The validation logic lived inside the UI screen, not in a shared layer.”
checking microphone…
- 2
문제: 모바일에서 같은 규칙을 쓰고 싶어도, 가져다 쓸 공통 코드가 아예 없었다. 규칙이 PC 화면 안에만 있으니까.
⚖️ Trade-off: 공통 함수가 없으니 모바일은 규칙을 또 새로 짜야 한다 → 같은 규칙이 두 곳에 따로 생김.
// One shared function both surfaces can call -- something to share ValidationResult Validate(LotId id) { var row = db.GetLot(id); return rules.Check(row); }🗣 영어로 말해“There was nothing to share, so PC and mobile drifted apart.”
checking microphone…
- 3
그래서 같은 상황인데 PC와 모바일 결과가 달랐다. 기능을 바꿀 때마다 한쪽만 고쳐져서, 변경의 약 20%가 화면끼리 안 맞는 버그로 나갔다.
⚖️ Trade-off: 두 곳을 따로 고치면 한 곳을 빼먹기 쉽다 → 작업자는 두 화면 다 못 믿게 됨.
🗣 영어로 말해“About one in five changes shipped a mismatch between the two screens.”
checking microphone…
- 4
해결: 규칙을 화면 밖, 공통 함수 한 곳으로 빼냈다. 그리고 화면 표 칸이 아니라 DB에서 지금 값을 새로 읽어서 판단하게 했다. PC도 모바일도 같은 함수를 부른다.
⚖️ Trade-off: 검사할 때마다 DB를 한 번 더 읽으니 살짝 느려지고 DB 부하가 는다.
✅ Fix: 화면에 떠 있던 옛날 값(stale)이 아니라 항상 최신 값으로 판단하니 정확해진다 → 그 한 번의 왕복은 정확성 값으로 받아들임.
🔧 도구:shared validation librarysingle entry point Validate(id)fresh DB read
ValidationResult Validate(LotId id) { var row = db.GetLot(id); // fresh DB state, not stale UI cells return rules.Check(row); // specialist's business rules } // PC and mobile call the same Validate(id)🗣 영어로 말해“I moved the rule into one shared function that reads fresh state from the DB.”
checking microphone…
- 5
역할을 나눴다. 나는 뼈대(공통 호출 구조, 보일러플레이트)를 짜고, 업무 규칙은 그 분야를 제일 잘 아는 전문가가 채웠다. 그리고 한 화면부터 천천히 켰다.
⚖️ Trade-off: 공통 라이브러리라 규칙 하나만 바꿔도 그걸 쓰는 모든 화면을 다시 배포해야 한다. 또 규칙에 버그가 있으면 모든 화면에 한꺼번에 퍼진다.
✅ Fix: 단위·통합 테스트로 받쳐 두고, 한 화면씩 단계적으로 켜서 문제가 터져도 범위를 작게(blast radius) 막았다.
🔧 도구:unit + integration testsphased rollout
// Boilerplate signature; specialist fills rules.Check; tested + phased [Test] void rejects_qty_over_max() { var result = Validate(lotId); // same entry point both surfaces use Assert.False(result.Ok); }🗣 영어로 말해“I wrote the boilerplate, the specialist wrote the rules, and we rolled it out one screen at a time.”
checking microphone…
- 6
결과: 화면끼리 안 맞던 그 버그 종류가 0이 됐다. 같은 입력이면 PC든 모바일이든 똑같은 결과가 나온다.
⚖️ Trade-off: 지금 방식은 '라이브러리'라 규칙 바꿀 때마다 쓰는 쪽이 전부 업그레이드해야 한다.
✅ Fix: 다음엔 라이브러리 말고 검사 '서비스'로 만들면, 한 곳만 고쳐도 쓰는 쪽은 그대로 둘 수 있다.
🔧 도구:validation service (next step)contract testsversioned rules behind flags
🗣 영어로 말해“That whole class of mismatch bugs went to zero — same input, same result everywhere.”
checking microphone…
- 7
한 줄 원칙: 같은 규칙이 두 화면에 다 필요하면, 그건 화면이 아니라 공통 층 한 곳에 있어야 한다. '화면에 있다'가 답이면 설계가 이미 틀린 거다.
🗣 영어로 말해“If a rule applies on two surfaces, it belongs in one shared layer, never the UI.”
checking microphone…
7단계 영어를 다 말하면 → 이 메커니즘 전체를 영어로 설명할 수 있게 된다.