최근에 올린 강의 글을 프로젝트에 적용한 결과다.
민감한 정보가 들어갈 수 있는
일기 내용을 데이터베이스에 암호화했다.
그리고 데이터베이스에서 가져올 때 복호화를 진행해 응답한다.
Attribute Converter 와 AES 암호화 모듈을 사용했다.
@DataJpaTest 으로 JPA 관련 설정을 불러와 이를 테스트했다.
모듈 구조
모듈 구조는 위와 같습니다.
adapter-postgres 에서 JPA 기술로 엔티티를 구현합니다.
ContentConverter 라는 이름으로 일기 내용을 암호화 / 복호화합니다.
이는 외부 PostgreSQL 데이터베이스와 통신합니다.
adapter-core 모듈에 PrivacyEncryptor 인터페이스를 정의했습니다.
같은 adapter 모듈 안에선 PrivacyEncryptor 인터페이스를 주입받습니다.
주입받는 객체는 런타임 시에 외부 보안 모듈에 정의된 객체가 됩니다.
// adapter/core/src/main/java/com/mashup/feelring/PrivacyEncryptor.java
package com.mashup.feelring;
public interface PrivacyEncryptor {
String encrypt(String raw) throws Exception;
String decrypt(String encrypted) throws Exception;
}
위 구조는 의존성 역전의 이점을 살렸습니다.
계층(레이어) 레벨은 아니지만, 모듈 레벨에서 의존성을 분리하고
의존성을 역전시켜 확장이 용이한 구조로 개선했습니다.
일기 내용을 암호화 / 복호화하는 ContentConverter
ContentConverter 코드는 아래와 같습니다.
package com.mashup.feelring.diary.converter;
import com.mashup.feelring.PrivacyEncryptor;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Converter
public class ContentConverter implements AttributeConverter<String, String> {
private final PrivacyEncryptor privacyEncryptor;
@Override
public String convertToDatabaseColumn(String raw) {
try {
return privacyEncryptor.encrypt(raw);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public String convertToEntityAttribute(String encrypted) {
try {
return privacyEncryptor.decrypt(encrypted);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
이전 강의글과 크게 다르지 않는 코드입니다.
이를 아래 엔티티 필드에 적용합니다.
package com.mashup.feelring.diary;
import com.mashup.feelring.diary.converter.ContentConverter;
import jakarta.persistence.*;
import java.time.LocalDateTime;
import lombok.*;
@NoArgsConstructor
@AllArgsConstructor
@Data
@Entity(name = "diary")
public class DiaryEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Convert(converter = ContentConverter.class)
private String content;
....
}
테스트 코드 또한 강의 글과 크게 다르지 않습니다.
package com.mashup.feelring.diary.converter;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;
import com.mashup.feelring.PrivacyEncryptor;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class ContentConverterTest {
@InjectMocks
private ContentConverter contentConverter;
@Mock
private PrivacyEncryptor privacyEncryptor;
@Test
void convertToDatabaseColumn() throws Exception {
final String privacyData = "사적이고 민감한 데이터입니다.";
final String expected = "암호화한 데이터";
when(privacyEncryptor.encrypt(privacyData)).thenReturn(expected);
final String encrypted = this.contentConverter.convertToDatabaseColumn(privacyData);
assertEquals(expected, encrypted);
}
@Test
void convertToEntityAttribute() throws Exception {
final String encrypted = "암호화한 데이터";
final String expected = "사적이고 민감한 데이터입니다.";
when(privacyEncryptor.decrypt(encrypted)).thenReturn(expected);
final String actual = this.contentConverter.convertToEntityAttribute(encrypted);
assertEquals(expected, actual);
}
}
마무리
모듈 단위에서 의존성 역전을 적용해 확장이 용이한 구조로 만들었습니다.
멀티 모듈로 만들다보니 어떻게 해야 확장이 용이할까? 를 고민하게 되네요.
암호화 모듈도 AES 뿐 아니라 다른 암호화 모듈도 추가될 가능성이 높다고 가정했습니다.
데이터베이스 엔티티 모듈도 PostgreSQL 뿐 아니라 MySQL, MongoDB, Redis 등도 될 수 있고요.
기존 코드를 변경하지 않고 모듈을 추가해 확장하는 식으로 설계 정책을 정하니,
코드 작성에 집중하기가 간편해졌네요.
또한 원하는 모듈을 가져와 사용하는 재미도 있습니다.
다음 글은 다른 내용으로 찾아뵙겠습니다.
'프로젝트 > 단기 프로젝트' 카테고리의 다른 글
[BetterDay] #3. AWS Route 53 & GoDaddy 도메인 연동 / HTTPS LetsEncrypt(Certbot) 설정 (0) | 2024.06.25 |
---|---|
[BetterDay] #1. 잡담 - 분리된 모듈에서 @DataJpaTest (1) | 2024.06.14 |
[커리어리 디스코드 봇] RSS XML에서 유효하지 않은 문자 제거 - Invalid bytes in character encoding (1) | 2024.05.04 |
커리어리 디스코드 봇 제작기 (0) | 2024.04.17 |
[Node.js] 도메인 접속 오류 / Typescript Declare Global 오류 / EAI_AGAIN getaddrinfo 오류 해결 방법 (0) | 2024.01.25 |
댓글