Changsoon's Note Backend Developer

select for update문

select for update문은 데이터베이스에서 동시성 제어를 위해 사용되는 SQL 구문으로, 트랜잭션에서 특정 레코드를 잠금으로써 다른 트랜잭션이 해당 레코드를 수정하지 못하도록 하는 기능이다.

select for update문을 실행하면 Lock을 획득하고, 해당 세션이 update 쿼리 후 commit하기 전까지 다른 세션들이 해당 row를 수정하지 못하도록 하는 기능이다.

기본 사용법

일반적으로 select for update 구문은 트랜잭션 안에서 사용되며, 데이터베이스에서 특정 레코드를 그 레코드에 대해 배타적 잠금을 설정한다.

예제

먼저 auto commit이 활성화 되어 있지는 않는지 확인해보자.

예를 들어서 user 테이블의 이름을 수정한다고 가정해보자.

세션 1에서 user 테이블의 id가 1인 row의 데이터의 lock을 획득한다.

이때 세션 2에서 해당 데이터를 수정을 하려고 시도할 때 기다리다 에러가 발생하는 것을 확인할 수 있다.

sql

세션 1에서 커밋을 한다면 세션 2에서도 데이터 수정이 가능한 모습을 볼 수 있다.

select for update문은 잠금을 하므로, 과도하게 사용하면 교착 상태나 성능 저하가 발생할 수 있기 때문에 필요한 경우에만 신중히 사용하는 것이 좋다.

우분투에서 SSH 접속하기

Ubuntu 환경에서 SSH 접속을 하는 방법에 대해서 알아보자.

SSH 서버 설치

먼저 접속하려는 서버와 로컬 서버 둘 다 SSH가 설치가 되어 있어야 한다.

보통 우분투를 설치할 때 openssh-server를 설치하는 경우가 많으니 먼저 설치되어 있는지 확인하자.

설치 확인 커맨드

설치되어 있지 않다면 설치를 하자.

SSH 서버 실행

설치 후 SSH 서버가 실행 중인지 확인

ssh 서버를 실행시키자.

SSH 접속하기

로컬 서버에서 대상 서버로 접속한다.

접속 명령어

처음 접속 시 호스트의 RSA 키 정보를 수락할지를 묻는 메시지가 뜰 수 있다.

yes 입력 후 비밀번호 입력

비밀번호 없이 접속하기

비밀번호 없이 SSH 키를 이용하여 자동 접속하려면 SSH 키 쌍을 생성하고 대상 서버에 복사해 사용할 수 있다.

SSH 키 생성 후 대상 서버로 SSH 공개 키 복사

자바 @FunctionalInterface

@FunctionalInterface는 Java 8에서 도입된 어노테이션으로, 람다 표현식과 메서드 참조에 사용될 수 있는 함수형 인터페이스임을 나타낸다.

함수형 인터페이스는 단 하나의 추상 메서드를 가지는 인터페이스를 말한다.

@FunctionalInterface를 통해 함수형 인터페이스를 정의하면, 컴파일러가 자동으로 해당 인터페이스에 하나의 추상 메서드만 존재하는지 확인해 주므로 실수를 방지할 수 있다.

사실 함수형 인터페이스에 @FunctionalInterface 어노테이션을 붙이지 않아도 같은 기능으로 작동한다.

하지만 컴파일러에서 하나의 추상 메서드만 있는지 검사하는 기능이 없다는 점과 이 인터페이스가 함수형 인터페이스라는 의미를 알 수가 없다는 점 때문에, 어노테이션을 적어주도록 하자.

예제

FunctionalInterface 정의는 @FunctionalInterface 어노테이션을 인터페이스에 붙여주면 된다.

default method는 여러개 선언 할 수 있다.

함수형 인터페이스 사용

파라미터와 리턴타입을 바꿔서 활용할 수 있다.

파라미터로 넣거나 리턴타입으로 함수형 인터페이스를 사용해서 활용할 수도 있다.

자바 record 클래스

자바 record 클래스는 자바 14에 처음 도입된 새로운 기능으로, 주로 불변 데이터 객체를 생성하는 데 사용된다.

record는 dto와 같이 데이터 값을 보관하는 객체를 생성할 때 유용하다.

record 사용 예시

record 클래스는 일반적인 클래스와 달리 equals(), hashCode(), toString() 메서드를 제공하고 getter 메서드를 제공한다.

타 개발 블로그에 직렬화(Serializable)도 자동으로 구현된다고 적혀 있는 경우가 종종 있는데 record클래스는 Serializable가 자동으로 구현되어 있지 않다. 스택오버플로우

DTO 객체에 활용

dto 객체에 record 클래스를 활용하면 여러 어노테이션을 붙이거나(롬복), 여러 클래스를 만드는 등 번거로움이 줄어든다.

예를 들어, 유저 생성 request 객체를 만든다고 가정한다면 이런 식으로 만들 수 있다.

기존의 롬복이나 getter, setter를 사용한 클래스 dto보다는 좀 더 깔끔한 형태로 만들 수 있다.

아니면 클래스에 여러 record 객체를 포함하여 좀 더 컴팩트하게 만들 수도 있다.

이런 식으로 특정 도메인의 Request 객체를 통합하여 한 번에 관리할 수 있다.

커스텀 메서드 사용

레코드 객체는 커스텀 메서드를 사용할 수 있다.

주로 개발할 때 dto 객체를 다른 dto로 변환하거나 할 때 사용한다.

예를 들어, presentation 계층과 application 계층의 구분을 두고, 각각의 dto를 사용하고자 할 때 사용한다.

@ConfigurationProperties

record 클래스를 사용하면 기존 properties 관리를 좀 더 효율적으로 할 수 있다.

JPA Entitiy?

record 객체를 써먹기에 딱 좋아 보이는 곳이 JPA 엔티티이다.

하지만 record 클래스는 엔티티 클래스로 사용하지 못하는데, 이유는 다음과 같다.

  1. record 객체는 불변 객체이기 때문에 JPA 영속성 컨텍스트에서 객체의 상태를 추적하고 변경할 수 있어야 한다. 하지만 record 불변이기 때문에 해당 작업을 수행하지 못한다.

  2. JPA 엔티티 클래스는 기본 생성자가 있어야 한다. 엔티티를 인스턴스화할 때 기본 생성자를 사용하며, 그 후에 필드를 채우거나 setter 메서드를 사용하여 값을 설정한다. 반면에, record 클래스는 자동 생성된 생성자만 제공한다. 이 생성자는 매개변수화된 생성자로, 기본 생성자를 제공하지 않는다. 따라서 문제가 발생한다.

  3. 쿼리 결과 처리 후 수행할 getter가 명명 규칙을 따르지 않는다. 예를 들어, getName()이 아니라, name()이다.

헥사고날 아키텍처

Hexagonal Architecture에 대해서 공부하고 해당 내용을 정리해보자.

먼저 헥사고날 아키텍처에 대해 알기 전에 계층형 아키텍처에 대해서 알아보자.

계층형 아키텍처란?

계층형 아키텍처는 시스템의 구성 요소를 여러 개의 계층으로 분리하여 각 계층이 특정 역할을 수행하도록 하는 패턴이다.

소프트웨어 개발에서 가장 일반적으로 널리 사용되고 있는 아키텍처다.

유지보수가 용이하고 테스트를 작성하기가 쉽다는 장점이 있다.

계층형 아키텍처의 문제

계층형 아키텍처의 정의를 들었을 때는 딱히 단점은 없어 보인다.

실제로 계층형 아키텍처는 견고한 아키텍처 패턴이다.

계층을 잘 이해하고 구성한다면 웹 계층이나 영속성 계층에 독립적으로 조메인 로직을 작성할 수 있다.

잘 만들어진 계층형 아키텍처는 선택의 폭을 넓히고, 변화하는 요구사항과 외부 요인에 빠르게 적응할 수 있게 해준다.

이런 계층형 아키텍처에 문제는 무엇일까?

  1. 계층형 아키텍처는 데이터베이스 주도 설계를 유도한다.
    • 정의에 따르면 전통적인 계층형 아키텍처의 토대는 데이터베이스다.
    • 웹 계층은 도메인 계층에 의존하고, 도멩ㄴ 계층은 영속성 계층에 의존하기 때문에 자연스레 데이터베이스에 의존하게 된다.
  2. 지름길을 택하기 쉬워진다.
    • 계층형 아키텍처에서 적용되는 규칙은 특정한 계층에서는 같은 계층에 있는 컴포넌트나 아래에 있는 계층에만 접근 가능하다는 것이다.
    • 만약 상위 계층에 위치한 컴포넌트에 접근해야 한다면 컴포넌트 계층을 아래로 내려버리면 된다. -> 지름길
  3. 테스트하기 어려워진다.
    • 엔티티의 필드를 단 하나만 조작하면 되는 경우에 웹 계층에서 바로 영속성 계층에 접근하면 도메인 계층을 건드릴 필요가 없지 않을까? 라고 생각할 수도 있다.
    • 한 두번은 괜찮지만 점점 해당 작업은 문제가 생긴다.
    • 단 하나의 필드를 조작하는 것이 불과하더라도 도메인 로직을 웹 계층에 구현하는 것은 아키텍처를 제대로 활용하지 못하는 것이며, 테스트를 작성할 때 웹 계층 테스트에서 다른 계층에 대한 의존성(모킹 등)이 발생할 수 있다.
  4. 유스케이스를 숨긴다.
    • 유스케이스가 간단해서 도메인 계층을 생략한다면 다른 계층에 존재할 수도 있기 때문에 새로운 기능을 추가할 적당한 위치를 찾기 어려워진다.
    • 계층형 아키텍처는 도메인 서비스의 너비에 관한 규칙을 강제하지 않는다.
    • 넓은 서비스는 영속성 계층에 많은 의존성을 갖게 되고, 다시 웹 레이어의 많은 컴포넌트가 이 서비스에 의존하게 된다.
  5. 동시 작업이 어려워진다.
    • 계층형 아키텍처는 여러 개발자가 동시에 작업하기가 어렵다.
    • 모든 것이 영속성 계층 위에 만들어지기 때문에 영속성 계층을 먼저 개발해야 하고, 그 다음에 도메인 계층, 마지막으로 웹 계층을 만들어야 한다.

계층형 아키텍처는 올바르게 사용하면 유지보수하기 쉬워지지만 많은 것들이 잘못된 방향으로 흘러가도록 용인한다.

헥사고날 아키텍처는 이러한 계층형 아키텍처의 문제를 해결해준다.

헥사고날 아키텍처란?

  • 헥사고날 아키텍처는 소프트웨어 설계 패턴 중 하나로, 소프르트웨어 애플리케이션을 비즈니스 로직과 외부 요소를 명확하게 분리해 독립성과 확장성을 높이려는 아키텍처다.
  • 헥사고날 아키텍처는 비즈니스 로직이 중심이 되어, 그 외부에 여러 개의 포트와 어댑터가 둘러싸고 있는 구조다.
  • 이를 통해 애플리케이션은 비즈니스 로직과 외부 시스템 간의 연결 방식을 변경하기 쉬워진다.
  • 핵심 비즈니스 로직이 독립적으로 구현되어 있어 외부 시스템과 무관하게 동작한다.
  • 외부 시스템과 독립적이기 때문에 테스트가 용이하며, 특정 포트나 어댑터만 교체해도 전체 시스템에 영향을 주지 않는다.
  • 클린 아키텍처에서는 설계가 비즈니스 규칙의 테스트를 용이하게 하고, 비즈니스 규칙은 프레임워크, 데이터베이스, UI 기술, 그 밖의 외부 애플리케이션이나 인터페이스로부터 독립적일 수 있다.
  • 이 아키텍처에서 가장 중요한 규칙은 의존성 규칙으로, 계층 간의 모든 의존성이 안으로 향해야 한다는 것이다.
  • 이러한 아키텍처는 대가가 따른다. 예를 들어 도메인 계층과 영속성 계층이 데이터를 주고 받을 때, 두 엔티티를 서로 변환해야 한다.
  • 하지만 다시 바꿔서 생각해보면 도메인 코드를 영속성 계층과의 결합이 제거된 상태이므로 바람직한 결과라고도 생각할 수 있다.

예제 코드

먼저 대표적인 헥사고날 아키텍처의 구조를 확인해보자.

alt text

이러한 아키텍처를 토대로 예제 코드의 구조를 설계한 구조이다.

alt text

먼저 Front에서 해당 헥사곤으로 API 요청을 할 수 있게 Adapter, port를 구현해보자.

다음으로 User 서비스에 접근할 수 있게 application 에서 usecase를 정의하자.

application 에 CreateUserService를 정의한다.

UserDomain의 로직을 가져다 사용한다.

헥사곤의 핵심인 UserDomain을 정의한다.

이제 도메인 로직을 처리했고 결과도 나왔으니 UserPersistencePort와 외부 Persistence 어댑터로 결과를 보내자.

외부 Persistence 코드를 정의한다.

패키지 구조 참고