Spring에는 3가지의 핵심 가치가 존재합니다.
IOC/DI
, AOP
, PSA
이렇게 총 3가지이죠!
오늘은 그 중에서 PSA에 대해서 알아보려고 합니다!
PSA는 Portable Service Abstraction 의 약자입니다.
그리고 이 단어는 Portable + Service Abstraction의 합성어이지요
그러면 차근차근 하나씩 알아가 봅시다
Service abstraction is a design principle that is applied within the service-orientation design paradigm so that the information published in a service contract is limited to what is required to effectively utilize the service[1] The service contract should not contain any superfluous information that is not required for its invocation
WIKI PEDIA - Service Abstraction
서비스 추상화는 실제로 서비스에서 사용되는 부분의 정보만을 제공하고, 불필요한 정보까지는 제공하지 않는다는 원칙입니다.
위키 백과는 이렇게 이야기 하는군요.
스프링에서는 추상화 계층을 이용해서 특정 기술의 구현부를 숨기고 개발자에게 편의성을 제공해 주는 것을 Service Abstraction 이라고 하죠.
추상화 계층을 통해서 어떤 기술은 내부적으로 숨기고, 개발자에게는 편의를 제공해 주는 것이 서비스 추상화라고 할 수 있습니다.
우리가 특정 기술을 사용한다고 해서 그 시술이 어떻게 구현되어있는지 모두 세세하게 알 수는 없겠죠(물론 알면 좋겠지만).
예를 들어 DB와 연동 작업이 필요해서 JDBC Driver를 사용한다고 했을 때, Jdbc의 세세한 구현 모두를 알아야 사용할 수 있는 것이 아닌 것 처럼 말이죠!
그렇다면 앞에 Portable 이 붙은 Service Abstraction은 무슨 뜻일까요??
위에서 설명드린 Service Abstraction으로 개발자에게 제공되는 기술을 다른 기술 스택으로 간단하게 변경할 수 있도록 확장성이 추가가 되었다고 보면 됩니다.
이렇게 해서 개발자는 기술 스택이 변경 되더라도 코드의 변경은 딱히 신경쓰지 않아도 되는 견고한 코드를 작성할 수 있게 해줍니다.
별 다른 수정 없이도 기술 전환 및 확장이 가능하다는 점에서 SOLID 원칙 중 OCP에 해당한다고 볼 수 있습니다.
@Controller
어노테이션을 통해서 우리는 웹 요청을 받는 컨트롤러를 만들 수 있습니다.
그리고 HttpServlet
클래스를 상속 받지 않고도 get
, post
등의 요청을 처리할 수 있게 됩니다.
바로 @GetMapping
, @PostMapping
등의 어노테이션의 도움을 받아서 처리할 수 있죠!
원래대로라면 HttpServlet
클래스를 상속 받고, doGet()
, doPost()
등의 메소드를 오버라이딩해야 하지만, 우리는 어노테이션을 메허드 위에 추가해주는 것으로 보다 간단하게 처리할 수 있습니다
그리고 Spring MVC에서 Spring Webflux로 의존성을 변경하면 기존 톰캣에서 netty 기반으로 바로 전환됩니다.
Spring Web MVC의 추상화 계층 덕분이죠!
트랜잭션이 필요한 매서드에서 작업을 할 때, @Transactional
어노테이션을 붙여서 작업합니다.
해당 어노테이션을 사용해 주면, 트랜잭션의 시작, 트랜잭션의 전파, 커밋, 롤백 등의 작업을 보다 쉽게 관리할 수 있게 도와주죠.
명시적으로 commit()
, rollback()
등을 호출하지 않았는지만, 트랜잭션이 마무리되면 커밋을 해주고, 중간에 트랜잭션이 깨지면 롤백을 해줍니다.
그리고 이러한 트랜잭션을 관리하는 TransactionManager
의 다양한 구현체들이 존재합니다.
개발자는 위의 구현체들중 원하는 구현체를 마음대로 사용해도 좋고, 언제든지 다른 구현체로 변경할 수 있습니다.
그리고 그 내부 구현은 몰라도 되죠!!
여기서 추상화 계층 예시를 하나 보겠습니다.
TransactionManager
의 자식 인터페이스 중 PlatformTransactionManager
는 추상화 계층에 속해 있습니다.
위 그림에서 눈여겨 볼 점은 해당 인터페이스를 구현한 비즈니스 로직 또한 추상화 계층의 일원으로 구현했다는 점입니다.
개발자는 여기서 PlatFormTransactionManager
를 사용하면 되고, 언제든지 다른 구현체로 변경할 수 있습니다.
참고로 아래와 같은 환경으로 프로젝트를 만들 경우, JpaTransactionManager
가 기본 구현체로 선택됩니다!!
application.yaml
spring:
datasource:
url: jdbc:mysql://localhost:3306/tmp?rewriteBatchedStatements=true&serverTimezone=Asia/Seoul&characterEncoding=UTF-8
username: root
password: root1234!
jpa:
database: mysql
database-platform: org.hibernate.dialect.MySQL8Dialect
generate-ddl: false
open-in-view: false
show-sql: true
hibernate:
ddl-auto: create-drop
properties:
jdbc:
hibernate:
format_sql: true
jdbc:
batch_size: 100
output:
ansi:
enabled: always
Penguin.java
간단한 엔티티
@Getter @Setter
@Entity
@Table(name = "penguins")
@NoArgsConstructor
public class Penguin {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private int age;
private float length;
private float weight;
private Species species;
@Builder
public Penguin(Long id, String name, int age, float length, float weight, Species species) {
this.id = id;
this.name = name;
this.age = age;
this.length = length;
this.weight = weight;
this.species = species;
}
}
PenguinRepository.java
JPA Repo 사용
@Repository
public interface PenguinRepository extends JpaRepository<Penguin, Long> {
}
PenguinService.java
save만 할 수 있는 단순한 Service
@Service
@RequiredArgsConstructor
public class PenguinService {
private final PenguinRepository repository;
@Transactional
public void addOnePenguin(Penguin penguin){
Penguin saved = repository.save(penguin);
System.out.println("================================");
System.out.println("Penguin added!!, ID : " + saved.getId());
}
}
Test.java
POJO 객체 생성 후, Repo에 객체 삽입
@SpringBootTest
public class TransactionalTest {
@Autowired
private PenguinService service;
@Test
void addOneTest() {
Penguin penguin = Penguin.builder()
.age(2)
.length(150)
.name("핑구")
.species(Species.EMPEROR_PENGUINS)
.weight(30)
.build();
service.addOnePenguin(penguin);
}
}
Transaction Manager 구현체
위와 같은 환경에서는 JpaTransactionManager
가 기본 구현체로 선택되어 있습니다.
구현체를 변경하기 위해서는 @EnableTransactionManagement
어노테이션을 사용한 다음 Config
에서 설정을 수정해줘야합니다!
캐싱 또한 PSA가 사용되는 부분입니다.
스프링에서는 @Cacheable
과 같은 어노테이션을 통해서 간단하게 캐싱을 적용시킬 수 있습니다.
그리고 캐싱은 다양한 구현체가 준비가 되어 있죠
위에 명시된 여러 구현체 중 마음에 드는 구현체를 가져다가 사용하면 되고, 언제든지 갈아끼우면 됩니다.