Study/Mastering-Spring-5-Study

ch14. 스프링 모범 사례

hongeeii 2023. 1. 17.
728x90
반응형

스프링 모범 사례

  • 엔터프라이즈 애플리케이션의 구조
  • 스프링의 구성
  • 의존 관계 버전 관리
  • 예외 처리
  • 단위 테스트
  • 통합 테스트
  • 세션 관리
  • 캐싱
  • 로깅

메이븐 표준 디렉토리 레이아웃

  • 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 대신 레디스에 저장된다.
    • 레디스는 인메모리 데이터 레파지토리다. 일반적으로 캐싱을 위한 빠른 데이터베이스나 메시지 브로커와 같은 많은 유스케이스 있다.
  1. 스프링 세션에 의존 관계를 추가한다.
  2. HttpSession을 스프링 세션으로 바꾸도록 필터를 구성한다.
  3. 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);
	}
}
 

캐싱 모범 사례

캐싱은 고성능 애플리케이션을 구축할 때 필수적이다.
외부 서비스나 데이터베이스에 항상 충돌하지는 않는다.
자주 변경되지 않는 데이터는 캐시될 수 있다.

  • 스프링은 캐시를 연결하고 사용하는 투명한 매커니즘을 제공한다.
  1. spring-boot-starter-cache 의존 관계를 추가한다.
  2. 캐싱 어노테이션을 추가한다.
@Component
public class ExampleRepository implements Repository {
	@Override
	@Cachealbe("something-cache-key")
	public Something getSomething(String id) {
	}
728x90
반응형

추천 글