인프런 커뮤니티 질문&답변

개발한입님의 프로필 이미지

작성한 질문수

백엔드 프레임워크 만들기

생각해볼 문제

세번째 생각해볼 문제에 대한 제 생각입니다. 피드백 부탁드려도될까요?

작성

·

433

0

1.  비즈니스로직에 SQL 이 섞이는 SQL 의존 문제를 해결하는 방법을 생각해보세요.

스프링 프레임워크의 `layered-architecture` 참고하자면, DB와 연관되는 부분들은 `Repository` 로 뺍니다.  

이 부분을 흔히 `Repository Pattern` 이라 하는데 이렇게 함으로써 디비를 사용하는 쪽은 `Repository` 안에서 처리하고 비즈니스 로직은 해당 값을 온전히 쓰는식으로 개발하면 될 것 같습니다.

2. 한번 생성한 Connection 객체를 재사용해야 하는 이유를 생각해보세요.

DB 연결과 같은 경우에는 계속해서 재사용하는 부분이기 때문에 싱글톤으로 둬서 재사용을 함으로써 시스템 자원을 효율적으로 다루기 위함. 

3. DB 에 데이터를 저장할때 휴대폰 번호 같은 민감정보는 암호화를 해야 합니다. 암호화 기능은 어디에 구현해야 하는지 생각해보세요.

이 부분은 제 생각에는 Framework에서 실제 암호화가 필요한 부분을 사용하고자할 때 사용해야된다고 생각합니다. 저희 코드로 보면 `SqlRunner` 에서 `HP_N` 을 업데이트하는 부분에서 사용자에게 받은 `param` 을 담기 전에 암호화를 수행하고 담은 다음에 처리를 해야된다고 생각합니다.

그 이유는 일단, DB 함수로도 암호화 처리가 가능하지만 이는 코스트를 높기 때문에 최대한 이러한 처리는 암호화가 필요한 DB의 UPDATE나 INSERT 시에 처리하는게 맞다고 생각합니다.

이 부분에 대해서 제로님께서는 어떻게 생각하시는지 궁금합니다.

4. SQL 은 데이터를 가공하는 DML, 스키마를 정의하는 DDL 그리고 DB 를 제어하는 DCL 로 구분합니다. 그리고 우리가 만든 SQL 기능은 DML뿐만 아니고 DDL, DCL 도 사용 가능합니다. 하지만 WAS 에선 DDL 과 DCL 사용을 금지해야 하는데 이유와 해결방법을 생각해보세요.

DDL, DCL을 사용하면 안되는 이유는 일단 웹어플리케이션 특성 상 불특정 다수가 사용할 수 있는 서비스들이 존재합니다. 이 때문에 만약 DDL 중에서도 `ALTER` 나 `TRUNCATE`  와 같은 치명적인 쿼리를 날릴 경우에 서비스 장애가 날 수 있다고 생각합니다.

DCL같은 경우에는 트랜잭션 관련된 질의와 권한 관련 질의가 있는데 권한 질의 (REVOKE, GRANT)의 경우에는 만약 db 접속 권한이 넘어가게 될 경우에 서비스에 치명적인 구멍이 될 수 있다고 생각합니다.

또한, 트랜잭션 질의인 (COMMIT, ROLLBACK)도 기존에 우리가 만들었던 transaction의 기능과 혼동의 소지가 있다고 생각합니다. 

이를 막기 위해서는 WAS에서 접속하는 DB 계정의 권한 설정을 

`GRANT SELECT, UPDATE, DELETE, INSERT ON EMP TO db-user` 이런식으로 둬서 필요한 권한만 사용하게끔 하는 것이 좋다고 생각합니다.

5. 트랜잭션직렬성은 우선순위가 부여된 여러개의 트랜잭션이 동시에 병행/병렬수행되더라도 결과는 순서대로 실행됨을 의미합니다. 하지만 트랜잭션직렬성이 보장되더라도 트랜잭션이 서로 얽혀 종료가 안되는 데드락이 발생할 수 있는데 프레임워크에서 이를 해결할 수 있는 방법에 대해 생각해보세요.

이 부분이 이번 섹션에서 가장 어려웠던 질문이었던 것 같습니다.

저는 이 문제를 봤을 때 키워드 2개를 떠올렸습니다.

1. Transaction Propagation

2. Locking or Transaction Isolation-level

처음에는 2번이 뭔가 맞을 것 같았는데 프레임워크에서 이를 해결할 수 있는 방법이라 하셔서 1번같기도하고? (Spring은 Propagation 설정을 지원하니..) 뭔가 2번은 DB로 처리하는 부분이고, 이 설정은 Spring Data JPA에서만 지원하니 1번같다는 느낌을 받았습니다.

혹시 모르니 제 생각을 적어보겠습니다.

1번과 같은 경우에는 스프링에서는 기본 값이 `REQUIRED`  로 되어있는 것으로 알고 있습니다. 전 회사에서는 스프링 like한 자체 프레임워크를 사용했었는데 이러한 복잡한 부모 <-> 자식 트랜잭션간의 처리가 어렵다보니 TPS가 중요했던 서비스가 아니였어서 `REQUIRES_NEW`  처럼 항상 트랜잭션을 새로 생성하여 독립성을 보장하는 식으로 작업을 했었습니다.

이렇게 되면 각각의 트랜잭션들의 독립성이 보장되니 데드락같은 문제를 해결할 수 않을까 조심스럽게 생각해봤습니다.

2번과 같은 경우에는 라킹을 통해서 트랜잭션을 보장하는 부분이라고 볼 수 있습니다.

전파는 트랜잭션이 묶이게 할껀지 아니면 독립적으로 볼 지에 대한 내용이라면,  라킹은 동시성에 대한 내용이라고 생각합니다.

mysql, mariadb는 `REPATABLE-READ` 가 기본값으로 세팅되어 있는데 조회된 내용이 항상 동일함을 보장할 수 있습니다.

그래서 제로님께서 해주신 질문의 의도는 데드락과 관련이 있고, 동시성과 관련이 있어보여서 이 부분이 좀 더 맞지않을까 생각이 드는데 잘 모르겠네요 ㅠㅠ 

근데 이는 프레임워크에서 처리한다는 개념보다 뭔가 DB에서 설정하는 느낌이라 혹시 위의 2개가 아니라 다른 의도를 가지고 질문을 하셨던건지 궁금합니다.

번외. 현재 필요한 것 보다 과하게 제품을 디자인 하는 것을 오버엔지니어링 이라고 합니다. 우리가 개발시 사용하는 인터페이스클래스가 오버엔지니어링이 되는 경우가 있는데 이를 생각해보세요.

이 부분의 인터페이스 클래스가 정말 자바의 인터페이스를 뜻하는 건지 아니면 큰 개념의 인터페이스인지를 제가 이해를 못했어서 두 가지 모두 생각해보았습니다.

1. 자바의 인터페이스의 오버엔지니어링 케이스

이 경우에는 구현하려는 인터페이스에 너무 많은 메서드가 있는 경우라고 볼 수 있을 것 같습니다. 

이를 테면 자동차의 `Tire` 라는 인터페이스가 존재하는데

  • 타이어교체하기()
  • 타이어공기압체크()
  • 타이어크기()
  • 타이어수명() 

등.. 사용하지 않을 메서드들이 많은 경우에 오버엔지니어링이지 않을까 생각이 듭니다.

2. 소프트웨어 관점에서의 Interface 오버엔지니어링 케이스

소프트웨어 관점에서 보면 인터페이스는 아이폰으로 치면 볼륨업, 볼륨다운, 전원버튼과 같이 필요한 부분만 노출 시키고 내부 구현은 사용자에게 모르게끔 하는 부분이라고 볼 수 있습니다.

이러한 케이스의 오버엔지니어링 케이스는 사용자에게 필요 없는 정보까지 노출되거나 (캡슐화 위반) 사용자가 의도치 않게 쓸 수 있는 부분이 노출되어 있거나 (휴대폰으로 치면 

이상한 버튼이 있는데 이걸 누르면 공장초기화가 된다던가?) 이러한 부분이라고 생각합니다.

지금까지의 예제에서는 현재는 SqlRunner가 `SQLiteJDBCTransaction` 을 사용하는데 실제 WAS 상에서 쓰기 위한 `SQLitePoolTransaction`  을 위한 코드들이 `SqlRunner` 상에 있습니다.

물론, 실제 사용될 코드이긴하나 만약 우리가 `SQLiteJDBCTransaction` 만 사용한다고 생각하면 이 부분이 오버엔지니어링이라 볼 수 있을 것 같습니다.

중요한 점은 이는 확장을 위한 설계이기 때문에 만약에 확장이 안된다는 가정이 있어야할거같은데 약간 어거지로 지금까지 예제로 적어보았습니다 ㅎㅎ; 

답변 4

1

제로님의 프로필 이미지
제로
지식공유자

안녕하세요. 제로입니다.

1번에 대한 추가 설명입니다.

표현하신 내용이 맞습니다.

표현하신 내용대로 
개발자의 java 코드 안에
SQL 기능을 사용하기 위한 JDBC 구현체나 SQL 구문이 안보이도록
프레임워크 기능으로 구현해 SQL 의존 문제를 해결했다고 생각하시면 됩니다.

마이바티스는 SQL 구문을 별도의 xml 파일로 빼서 java 코드에 있는 SQL 의존성을 제거했고 
JPA 는 SQL 구문 자체를 자동으로 생성시키는 전략으로 java 코드에 있는 SQL 의존성을 제거했습니다.

그리고 우리가 만든 프레임워크는

FW_SQL 테이블에 SQL 구문을 보관하고
트랜잭션 5가지 상태를 제어하는 Transaction 클래스
SQL 실행에 필요한 데이터를 횡단관점으로 제공하는 Box 
논리적 테이블을 받아오는 자료구조 Table 을 조합
CRUD 기능을 제공하는 SqlRunner 클래스를 만들어

비즈니스로직에 SQL이 섞이는 SQL 의존 문제를 해결할 수 있었습니다.

보일러플레이트(반복되는 작업을 자동화 해주는 기능) 키워드를 적어 주셨는데
FW_SQL 에 저장된 SQL 은 정적SQL 실행을 자동화 해주는 개념보다는
java 코드에 SQL 구문을 이동시킨 개념이라고 보는게 맞을 것 같습니다.

추가로 개정판에서는
FW_SQL 테이블을 사용하지 않고 파일에 저장하는 정책을 사용합니다. 

제가 실무에선 테이블을 잘 사용해서 문제 없다고 생각했는데
어려워 하시는 분들이 있어 파일 저장으로 변경했습니다.


2번에 대한 추가 설명입니다.

가령 a1, a2 테이블을 T1, T2 트랜잭션이
서로다른 순서대로 UPDATE 하는 상황이 있다고 가정해 보겠습니다.

T1 -> a1 (UPDATE 완료, 점유)
T1 -> a2 (UPDATE 진행중, 대기)

T2 -> a2 (UPDATE 완료, 점유)
T2 -> a1 (UPDATe 진행중, 대기)


그림으로 표현하면 다음과 같고



바로 이런 상황이 환형대기(순환대기, Circular wait) 상태인겁니다.


상호배제, 점유와대기, 임계영역은 
데이터의 무결성과 일관성을 위해 건드리면 안되기 때문에

환형대기에 빠지지 않도록 예방을 하고 이를 통해 데드락을 제거할 수 있습니다.

프레임워크에서 환형대기가 성립되지 않도록 제어하는건 단순합니다.

UPDATE 순서를 미리 a1, a2 로 정의하고
(순서는 테이블 이름 또는 중요도에 따라 우선순위를 부여합니다.)

개발자가 만든 코드가
그 규칙에 맞지 않으면 rollback 처리를 하면 되는거죠.

바로 위의 예처럼
T2 가 실행될때 환형대기가 발생할 가능성이 있음을 예측하고
오류(rollback)를 발생시키면 되는겁니다.

프레임워크의 기능으로 구현한다면

UPDATE a2 set name = 'ABC'  구문이 실행될때

SQL 구문을 분석해
a2 가 UPDATE 된다는 정보를 가지고 있다가

그 다음 실행되는 SQL 구문을 분석해 
a1 을 UPDATE 시도한다고 하면 데드락이 발생될 가능성이 있기 때문에
Exception 을 발생시켜 rollback 하면 됩니다.


검색으로 찾아보신 내용 맞구요.
추가로 관련된 지식은 뷰직렬, 충돌직렬이 있습니다.
한번 찾아보시면 데드락을 방지하는데 도움되실 겁니다. ^^

질문하신 내용중에 사례를 물어보셔서
많이 사용되는 프레임워크기준
데드락 감지하는 기능이 있는지 찾아봤는데 찾기가 힘드네요.   

만약 없다면 
이번 기회에  
사용하시는 프레임워크에서 
환형대기를 감지하고 회피하는 기능을 추가 하시는 것도 좋은 경험이 될 것 같습니다.

1

개발한입님의 프로필 이미지
개발한입
질문자

항상 상세한 답변 감사드립니다 제로님 ㅎㅎ

이번에 해주신 피드백에서는 제가 잘 이해가 안되는 부분이 몇 군데 있어서 추가 질문을 올리게 되었습니다.

1. 비즈니스로직에 SQL이 섞이는 SQL 의존 문제를 해결하는 방법을 생각해보세요. 

이 부분에 답변을 해주신 부분이 개발자의 코드가 아닌 프레임워크의 기능으로 제공해야한다고 말씀해주셨는데 

저희 강의 예시로 보면 FW_SQL 내부에 보일러플레이트와 비슷한 느낌의 SQL을 넣어두고 이걸 정적 쿼리로 바꿔주는 부분으로 생각을 했는데 이게 맞을까요?

더 나아가서 Spring Data JPA 에서 findBy~~ 와 같은 형식도 마찬가지인지 궁금합니다.

5. 트랜잭션직렬성은 우선순위가 부여된 여러개의 트랜잭션이 동시에 병행/병렬수행되더라도 결과는 순서대로 실행됨을 의미합니다. 하지만 트랜잭션직렬성이 보장되더라도 트랜잭션이 서로 얽혀 종료가 안되는 데드락이 발생할 수 있는데 프레임워크에서 이를 해결할 수 있는 방법에 대해 생각해보세요.

이 부분에서 환형대기에 대한 처리부분을 프레임워크가 지원한다고 말씀해주셨는데 말씀하신 내용을 이해했는데 궁금한 부분이 생겼습니다. 이 부분에 대해서 검색을 해봤는데 제가 잘못 짚은건지 나오지는 않더라구요 ㅎㅎ

일단, 제일 궁금한 건 실제 사용되는 상용 프레임워크에서 환형대기를 막을 수 있는 것들이 어떤게 있는지 궁금합니다. 특히, DML이 순서를 어떻게 지키는지 감시하는 부분이 제일 궁금했습니다.

이것저것 찾아보다가, https://www.ikpil.com/1348 이러한 글을 읽었는데 이 글에 적힌거처럼 락의 단방향을 유지함으로써 DML의 순서를 보장시키는 걸까요? 

1

제로님의 프로필 이미지
제로
지식공유자

안녕하세요. 제로입니다.
섹션 3부터 난이도가 높아지는데
섹션 3의 생각해볼 문제를 적어 주셔서 감사합니다.

글 쓴 내용을 보면 닉네임하고는 정 반대의 개발자 이신 것 같습니다. ^^


1.  비즈니스로직에 SQL 이 섞이는 SQL 의존 문제를 해결하는 방법을 생각해보세요.

대부분의 프레임워크는 개발자의 코드에 SQL 을 섞이지 않도록 훌륭한 기능을 제공합니다.

그리고 이에 그치지 않고
말씀하신 내용대로 
repository, persistent 또는 DAO 계층을 분리하고 모듈화 해서 사용합니다.

그런데 그런 노력에도 SQL 의존문제는 발생합니다.

날짜 데이터를 사람이 보기 편하게 형식화 하거나
민감한 정보를 마스킹 하는 표현의 문제를 시작으로
DB 암호화 위치에 대해 고민하기도 하고
SQL 인젝션을 방어하기 위한 문자열 치환 문제도 개발자의 코드를 어렵게 합니다.

repository 영역에 위 코드를 적용해야 하는지
아니면 서비스 영역에 적용해야 하는지 고민하게 되는거죠.

따라서 SQL 의존문제는 SQL 기능을 추상화 하는데 그치지 않고
위의 문제를 개발자가 고민하지 않아도 해결 할 수 있도록
개발자의 코드가 아닌 프레임워크 기능으로 제공해야 합니다.


2. 한번 생성한 Connection 객체를 재사용해야 하는 이유를 생각해보세요.

Connection 객체가 생성되고 첫 SQL 이 실행될때
DBMS 접속을 위한 N/W 연결 수립, 접근제어 정책 실행
그리고 접속한 클라이언트가 사용할 자원이 DBMS 에서 생성됩니다.

그렇기 때문에 한번 생성된 Connection 객체는 버리지 말고 재사용 해야 하는거죠.

디자인 패턴 관점으로는
멀티스레드 환경에선 Connection POOL 을 구성할 수 있는 Flyweight Pattern
단일스레드 환경에선 단 한개의 객체만 생성할 수 있는 싱글톤 패턴으로 

한번 만들어진 Connection 객체를 재사용 합니다.


3. DB 에 데이터를 저장할때 휴대폰 번호 같은 민감정보는 암호화를 해야 합니다. 암호화 기능은 어디에 구현해야 하는지 생각해보세요.


암호화키와 암호화 연산의 위치에 따라 WAS, DB 그리고
HSM (HW 로 격리된 암호화 장비) 로 암호화 기능 사용을 구분할 수 있습니다.


말씀하신대로 암호화 실행 비용(코스트)를 생각해 본다면
DB 서버가 아닌 WAS 에 기능을 구현하는게 맞구요.


다른 관점으로 암호화된 데이터의 저장과 사용 시점의 분리를 생각한다면 
비즈니스 로직이 수행되는 WAS
또는 물리적으로 암호화 기능이 보호되는 HSM 에 두는게 맞습니다.

만약 HSM 장비를 사용할 수 없다면 
접근제어가 실행되는 WAS 영역에 비밀키를 잘 보관하고 암호화 기능을 실행하면 됩니다.

하지만 이에 그치지 않고 프레임워크 영역에 다양한 암호화 기능을 제공하고
이를 쉽게 사용할 수 있게 해주면 민감정보를 잘 보호할 수 있습니다.


4. SQL 은 데이터를 가공하는 DML, 스키마를 정의하는 DDL 그리고 DB 를 제어하는 DCL 로 구분합니다. 그리고 우리가 만든 SQL 기능은 DML뿐만 아니고 DDL, DCL 도 사용 가능합니다. 하지만 WAS 에선 DDL 과 DCL 사용을 금지해야 하는데 이유와 해결방법을 생각해보세요.

잘 이야기 해주셨습니다. 
의외로 이런 문제가 프로젝트 종료 시점에 발견이 되는 경우가 많은데 
아키텍쳐 구성부터 고려해야 할 대상입니다.


5. 트랜잭션직렬성은 우선순위가 부여된 여러개의 트랜잭션이 동시에 병행/병렬수행되더라도 결과는 순서대로 실행됨을 의미합니다. 하지만 트랜잭션직렬성이 보장되더라도 트랜잭션이 서로 얽혀 종료가 안되는 데드락이 발생할 수 있는데 프레임워크에서 이를 해결할 수 있는 방법에 대해 생각해보세요.

제가 생각 못한 키워드를 적어주셔서 감사하다는 이야기를 드리고 싶습니다.
다른 사람의 이야기를 듣고 생각하며 내 지식을 깊고 넓게 할 수 있음을 다시 한번 깨닫게 되네요.

알려주신 키워드로 이야기를 풀어가면

1. Transaction Propagation
이부분은 하나의 서비스 로직을 실행할때 트랜잭션을 여러개 사용할지 여부를 프레임워크를 통해 제어하는 기능입니다.

그런데 이런 기능은 트랜잭션의 원자성을 보장하기 힘들고
문제가 발생시 추적이 어려워지는 문제가 있습니다.

프레임워크의 기능으로는 훌륭하지만 개발자가 사용하기는 난해한 기능인거죠.

따라서 저희가 만드는 프레임워크는 무조건 하나의 트랜잭션만 사용하도록 합니다.
이를 통해 개발자는 ALL or NOTHING 메커니즘을 신경쓰지 않아도 알아서 실행되도록 합니다.

아쉽게도 Transaction Propagation 를 사용해 트랜잭션을 분리 또는 합쳐도 트랜젝션의 락은 발생하기 때문에
데드락은 해결할 수 없습니다.

2. Transaction Isolation-level

"Transaction Isolation-level"은
성능과 일관성의 트레이드오프(한 속성이 좋아지만 다른 속성이 나빠지는) 를
DBMS 상에서 조정하는 기능입니다.


그렇기 때문에 Isolation-level 를 조정하다고 해도 특정 레코드 또는 컬럼을 잠그는 Locking 은 사용되고
Locking 이 사용되기 때문에 데드락은 발생합니다.

DBMS  관점 좋은 키워드를 적어 주셨는데 데드락을 해결할 수 없다고 생각을 하구요.

제 관점에서 프레임워크에서 데드락을 해결할 수 있는 방법을 이야기 하면

데드락은
상호배재, 점유와대기, 비선점, 환형대기
4가지 조건이 동시에 실행될때 발생합니다.

따라서 4 가지 조건중 하나만 제거해도 데드락은 예방이 되는데

프레임워크에선 이중 환형대기를 감지하고 제거하도록 기능으로 구현되어야 합니다.

정규화된 테이블의 DML 순서를 지키도록 감시한다거나
SQL 실행시 타임아웃을 설정해 의도치 않은 대기를 제거한다거나
로그를 친절하게 기록해 데드락이 발생했을때 빠르게 코드를 수정할 수 있는 기회를 줘야 하는거죠.


번외. 현재 필요한 것 보다 과하게 제품을 디자인 하는 것을 오버엔지니어링 이라고 합니다. 우리가 개발시 사용하는 인터페이스클래스가 오버엔지니어링이 되는 경우가 있는데 이를 생각해보세요.

잘 이야기 해주셨습니다.

다른 관점의 제 생각을 이야기 드리면

A 가 B 의 기능을 사용할때 A 는 B 에 의존한다고 이야기 합니다.

A 가 B 에 의존할때 높은 결합도는 파급효과가 발생했을때 제거 비용이 커집니다.
그래서 인터페이스를 중간에 둬 낮은 결합도를 유도하고
교체를 통해 파급효과 제거 비용을 줄이게 되는데

문제는 교체의 기회가 없는 구현클래스와 인터페이스가 1:1 관계일때
인터페이스 사용이 오히려 개발 비용을 늘리는 경우를 많이 봤습니다.

그래서 이부분을 오버엔지니어링의 사례로 이야기 드리고 싶었고
구현 클래스와 인터페이스가 1:1 관계일때 인터페이스를 제거하라는 이야기도 하고 싶어서
질문을 하였습니다.

0

개발한입님의 프로필 이미지
개발한입
질문자

와.. 친절한 답변 감사드립니다. 

환형대기 회피라.. 학부시절에 c로 예제만 짜본 수준인데 이번에 한번 도전과제로 생각해보 구현할 수 있으면 구현해보도록 하겠습니다 ㅎㅎ

주말인데도 답변해주셔서 너무나 감사드립니다!!