ch14. 스프링 모범 사례
스프링 모범 사례
- 엔터프라이즈 애플리케이션의 구조
- 스프링의 구성
- 의존 관계 버전 관리
- 예외 처리
- 단위 테스트
- 통합 테스트
- 세션 관리
- 캐싱
- 로깅
메이븐 표준 디렉토리 레이아웃
- src/main/webapp : 웹 애플리케이션과 관련된 모든 리소스, 뷰 파일 (jsp, 뷰 템플릿, 정적 콘텐츠)
- src/test/resource : 단위 테스트와 관련된 모든 리소스
레이어 아키텍처를 사용한 애플리케이션 구축
핵심 설계 목표 중 하나는 SoC(seperation of Concerns)이다. - 관심사의 분리
애플리케이션이나 마이크로서비스의 크기와 관계없이 레이어 아키텍처를 만드는 것이 좋다.
애플리케이션을 레이어화하면 단위 테스트가 단순해진다. 레이어의 코드마다 다음 레이어를 모킹해 단위 테스트를 완벽하게 수행할 수 있다.
- 다른 레이어를 위한 별도의 컨텍스트 파일
- 레이어마다 다른 스프링 컨텍스트를 사용하는 것이 좋다. 각 레이어의 문제를 분리하는 데 도움이 되며 특정 레이어의 코드를 단위 테스트할 때도 도움이 된다.
- application-context.xml
- presentation-context.xml - PresentationConfig.java
- services-context.xml - ServicesConfig.java
- business-context.xml - BusinessConfig
- persistence-context.xml
예외 처리 모범 사례
- 체크된 예외 : 서비스 메소드가 체크된 예외를 발생시키면 모든 소비자 메소드가 예외를 처리하거나 예외를 발생시켜야 한다. (ex try-catch)
- 언체크된 예외 : 소비자 메소드는 서비스 메소드에 의해 발생한 예외를 처리하거나 발생시킬 필요가 없다. (ex. RuntimeException)
RuntimeException과 모든 서브 클래스는 언체크된 예외이며 다른 모든 예외는 체크된 예외다.
try{
File file = new File("my/file/path");
FileInputStream fis = new FileInputStream(file);
}catch(FileNotFoundException e){
}
- 예외 처리에 대한 스프링의 접근 방식
- 스프링의 예외 대부분은 언체크돼 있어 코드가 단순해진다.
jdbcTemplate.udpate(INSERT, bean.getDescription, bean.isDone());
- 권장 예외 처리 방법
- 소비자를 생각해보자. 메소드 소비자가 예외에 대해 유용한 작업을 수행할 수 없는 경우 ( 로깅 또는 에러 페이지 표시 제외) 예외 발생 처리를 하지 않는다. (언체크된 예외 제외)
- ex. catch(exception e) e.printstacktrace(); / log.error("error has caught", e);
- 최상위 레이어 (일반적으로 프레젠테이션 레이어) 에서는 에러 페이지를 표시하거나 소비자에게 에러 응답을 보내려면 모든 예외 처리를 잡아야 한다.
- 스프링 구성 간결하게 유지
- 어노테이션 이전의 스프링 문제는 애플리케이션 컨텍스트 XML 파일의 크기였다.
- 어노테이션을 사용하면서 긴 애플리케이션 컨텍스트 XML 파일이 더 이상 필요하지 않게 됐다.
- 빈을 수동으로 XML 파일에 연결하는 대신, 컴포넌트 스캔을 사용해 빈을 찾고 오토와이링하는 것이 좋다.
- 애플리케이션 컨텍스트 XML 파일은 간결하게 유지해야 한다.
- 프레임워크 관련 구성이 필요할 때마다 자바 @Configuration을 사용하는 것이 좋다.
ComponentScan 에서 basePackageClasses 속성 사용
- 컴포넌트 스캔을 사용할 때 basePackageClasses 속성을 사용하는 것이 좋다.
@ComponentScan(basePackageClasses = ApplicationController.class)
public class SomeApplication {
basePackageClasses 속성은 basePackages()의 type-safe 대안이며 어노테이션이 달린 컴포넌트를 스캔할 패키지를 지정하는 데 사용할 수 있다.
필수 의존 관계에 생성ㅇ자 주입 선호
- 필수 의존 관계 : 빈에 사용하려는 의존관계다. 의존 관계를 사용할 수없을 때는 컨텍스 로드되지 않는 편이 좋다. - 생성자 주입
- 선택적 의존 관계 : 이들은 선택적 의존 관계로 항상 이용할 수 있지는 않다. 컨텍스트를 사용할 수 없는 경우에도 컨텍스트를 로드하는 것이 좋다. -setter 주입
setter 주입 대신 생성자 주입을 사용해 필수 의존 관계를 연결하는 것이 좋다.
필수 의존관계가 누락되면 컨텍스트가 로드되지 않도록 한다.
public class SomeClass() {
private MandotoryDependency mandatoryDependency;
private OptionalDependency optionalDependency;
public someClass(MandatoryDependency mandatoryDependency) {
this.mandatoryDependency = mandatoryDependency;
}
public void setOptionalDependency(OptionalDependency optionalDependency) {
this.optionalDependency = optionalDependency;
}
스프링 프로젝트의 의존 관계 버전 관리
프로젝트는 의존 관게라고도 하는 다른 프레임워크에 의존한다. 사용되는 프레임워크 의존 관계 버전을 관리하는 것은 중요하다.
스프링 부트를 사용할 떄 의존 관계 버전을 관리하는 가장 간단한 옵션은 spring-boot-starter-parent를 부모 POM으로 사용하는 것이다.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring-boot.version}</version>
</parent>
200개가 넘는 의존 관계의 버전은 spring-boot-starter-parent에서 관리한다. 스프링부트가 릴리스되기 전에는 의존 관계버전이 모두 잘 작동되는 것을 보장한다.
단위 테스트 모범 사례
- 비즈니스 레이어 테스트
- 비즈니스 레이어의 테스트를 작성할 때 단위 테스트에서 스프링 프레임워크를 사용하지 않는 것이 좋다.
- 테스트가 프레임워크에 독립적이며 더 빠르게 실행되기 때문이다.
@Runwith(MockitoJunitRunner.class)
public class BusinessServiceTest {
private static final User user = new User("User");
@Mock
private DataService dataservice;
@InjectMocks
private BusinessService service = new BusinessServiceImpl();
@Test
public void testCalculateSum() {
BDDMockito.given(dataService.retrieveData(Matchers.any(User.class)))
.willReturn(Arrays.asList(Arrays.asKist(new Data(10), new Data(15), new Data(25)));
long sum = service.calculateSum(user);
assertEquals(10 + 15 + 25, sum);
}
}
- 스프링 프레임워크는 실행 중인 애플리케이션의 의존 관계를 연결하는 데 사용된다.
- 그러나 단위 테스트에서 @InjectMocks 모키토 어노테이션을 @Mock와 함께 사용하는 방식이 가장 좋다.
- 웹 레이어 테스트 작성
- 웹 레이어의 단위 테스트에는 컨트롤러 테스트가 포함된다
- 스프링 MVC에 구축된 웹 레이어에 Mock MVC 사용하기
- Jersey 테스트 프레임워크는 Jersey 및 JAX-RS를 사용해 구축된 REST 서비스에 적합하다.
@Runwith(SpringRunner.class)
@WebMvcTest(TodoController.clasS)
public Class TodoControllerTest {
@Autowired
private MockMvc mvc;
@MockBean
private TodoService service;
//
}
@WebMvcTest를 사용하면 오토와이어 MockMvc를 사용하고 웹 요청을 실행할 수 있다.
@WebMvcTest의 가장 큰 특징은 컨트롤러 구성요소만 인스턴스화 한다는 것이다.
다른 모든 스프링 구성요소는 모킹될 것으로 예상되며 @MockBean을 사용해 자동 연결될 수 있다.
- 데이터 레이어 테스트 작성
- 스프링 부트는 데이터 레이어 단위 테스트를 위한 간단한 어노테이션 @DataJpaTest를 제공한다.
@DataJpaTest
@Runwith(SpringRunner.class)
public class UserRepositoryTest{
@Autowired
UserRepository userRepository;
@Autowired
TestEntityManager entityManager;
@DataJpaTest는 테스트용으로 특별히 설계된 표준 JPA EntityMangaer에 대안을 제공하는 TestEntityManager 빈을 삽입할 수 있다.
@DataJpaTest 외부에서 TestEntityManager를 사용할 때 @AutoConfigureTestEntityManager 어노테이션을 사용할 수 있다.
통합 테스트 모범 사례
단위 테스트는 특정 레이어를 테스트하지만 통합 테스트는 여러 레이어의 코드를 테스트하는 데 사용된다.
테스트를 반복적으로 유지하려면 통합 테스트에 실제 데이터베이스 대신 임베디드 데이터베이스를 사용하는 것이 좋다.
임베디드 데이터베이스를 사용해 통합 테스트를 수행할 때는 별도의 프로파일을 만들기를 권한다.
app.profiles.active : production
application-production.properties
app.jpa.database : MYSQL
app.datasource.url : <<VALUE>>
app.datasource.username : <<VALUE>>
app.datasource.password : <<VALUE>>
@ActiveProfiles("application-production")
@Runwith(SpringRunner.class)
@SpringBootTest(classes = Application.class, WebEnvironment.RANDOM_PORT_
public class TodoControllerIT {
@LocalServerPort
private int port;
private TestRestTemplate template = new TestRestTemplate();
스프링 세션을 이용한 세션 관리
세션 상태 관리는 웹 애플리케이션을 배포하고 확장할 때 중요한 과제다.
HTTP는 무상태 프로토콜이다. 웹 애플리케이션과의 사용자 상호 작용 상태는 일반적으로 HttpSession에서 관리된다.
- 모든 Rest API를 무상태로 만드는 것이 좋다.
- 세션에 가능한 적은 데이터를 보유해야 한다.
- 세션에서 필요하지 않은 데이터를 식별하고 제거하는 데 집중
- 스프링 세션은 세션 레파지토리를 외부화하는 기능을 제공한다.
- 스프링 세션은 로컬 HttpSession을 사용하는 대신 그림과 같이 세션 상태를 다른 데이터 레파지토리에 저장하는 대안
- 스프링 세션은 명확한 SoC를 (관심사의 분리)를 제공한다. 애플리케이션 코드는 사용 중인 세션 데이터 레파지토리와 상관없이 똑같이 유지된다.
- 구성을 통해 세션 데이터 레파지토리 간에 전환할 수 있다.
- 레디스로 스프링 세션 구현
- 예시에서는 레디스 세션 레파지토리를 사용하기 위해 스프링 세션을 연결한다. 세션에 데이터를 저장하는 코드는 똑같이 유지되지만 데이터는 HTTP 대신 레디스에 저장된다.
- 레디스는 인메모리 데이터 레파지토리다. 일반적으로 캐싱을 위한 빠른 데이터베이스나 메시지 브로커와 같은 많은 유스케이스 있다.
- 스프링 세션에 의존 관계를 추가한다.
- HttpSession을 스프링 세션으로 바꾸도록 필터를 구성한다.
- AbstractHttpSessionApplicationInitializer를 상속해 톰캣에 필터링을 활성화한다.
- Redis
- Rdis(레디스)는 키-값 구조의 비정형 데이터를 저장하고 관리하기 위한 비 관계형 데이터베이스 관리 시스템으로, String, Set, Sorted Set, Hash, List 자료구조를 지원한다.
- 데이터의 저장을 메모리에 하는 In-Memory기반의 DB이기 때문에 속도가 매우 빠르다는 장점이 있어, 캐시 및 세션 저장소로 많이 사용한다.
// spring에서 redis에 대한 의존성
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
// spring에서 redis를 session storage로 사용하기 위한 의존성
implementation 'org.springframework.session:spring-session-data-redis'
spring :
session :
storage-type : redis
redis :
host : localhost
port : 6379
@Configuration
public class RedisConfig {
@Value("${spring.redis.host}")
public String host;
@Value("${spring.redis.port}")
public int port;
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setConnectionFactory(connectionFactory);
return redisTemplate;
}
@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
configuration.setHostName(host);
configuration.setPort(port);
return new LettuceConnectionFactory(configuration);
}
}
캐싱 모범 사례
캐싱은 고성능 애플리케이션을 구축할 때 필수적이다.
외부 서비스나 데이터베이스에 항상 충돌하지는 않는다.
자주 변경되지 않는 데이터는 캐시될 수 있다.
- 스프링은 캐시를 연결하고 사용하는 투명한 매커니즘을 제공한다.
- spring-boot-starter-cache 의존 관계를 추가한다.
- 캐싱 어노테이션을 추가한다.
@Component
public class ExampleRepository implements Repository {
@Override
@Cachealbe("something-cache-key")
public Something getSomething(String id) {
}
'Study > Mastering-Spring-5-Study' 카테고리의 다른 글
ch13. 리액티브 프로그래밍(Reactive Programming) (0) | 2023.01.17 |
---|---|
ch12. 마이크로서비스 구축하기(MSA) (0) | 2023.01.16 |
ch11. 마이크로서비스 시작하기(MSA) (2) | 2023.01.16 |
ch10. 스프링_데이터_JPA (1) | 2023.01.16 |
ch08. 스프링 시큐리티를 활용한 시큐리티 REST API (0) | 2023.01.15 |
댓글