PSA( Portable Service Abstraction )
。특정 기술과 관련된서비스를추상화하여 일관된 방식으로 사용할 수 있게 하는 기능
▶소스코드 변경이 없이환경설정만 변경하여다른 서비스를 이용할 수 있도록함
。서비스가 다르더라도추상화를 통해서비스를 하나로 묶어서개발자가 동일한메소드로 사용할 수 있게 해주는Spring의추상화 계층
▶추상화 계층을 통해기술을 내부에 숨기고 개발자에게 편의성을 제공
。주로 사용하는PSA 어노테이션:
▶@Transactional: 어떤DB를 쓰던DB Transaction을 보장
▶@Cacheable: 어떤캐시를 사용하던캐싱을 보장하는어노테이션
。OCP 원칙 준수:PSA적용 시 기존에 작성된 코드를 수정하지않고 확장이 가능
。동일PSA 인터페이스를 구현하는Spring Bean를 다른Spring Bean로 교체 시소스코드를 변경하지 않고application.yml의환경설정을 변경하여Spring Bean을 교체할 수 있음
PSA원리
。PostegreSQL,MySQL등의 각각 다른DB들에 대해DAO역할을 수행하는Class 구현체들을Spring에서는추상화를 통해 공통된PSA 인터페이스를 구현하도록하여 하나로 묶어서 동일한method로구현체를 다룰 수 있도록한다.// PSA 인터페이스 interface jdbcConnector{ void getConnection(); } @Repository class OracleJdbcConnector implements jdbcConnector{ @Override public void getConnection(){} } @Repository class SQLiteJdbcConnector implements jdbcConnector{ @Override public void getConnection(){} } @Repository class MariaDBJdbcConnector implements jdbcConnector{ @Override public void getConnection(){} } @Service public class DbClient { private final jdbcConnector connector; public DbClient(jdbcConnector connector){ this.connector = connector; } }。단. 위 코드의 문제점으로
DbClient에서생성자 주입시 적합한Spring Bean을 찾을 수 없는 오류가 발생.
▶의존객체에Spring Bean후보가 3개 발생.
。@Primary,@Qualifier("클래스명"),이름 주입의 방법으로소스코드를 변경하여Spring Bean을 설정하거나Configuration file을 수정하여Spring Bean을 설정
PSA를 통해의존객체에PSA 인터페이스 구현체의의존성 주입시Spring Context내 이를 구현하는 복수의Spring Bean이 존재 시 사용하는 방법
。gradle의Configuration file(application.ymlorapplication.properties)에Spring Bean 명칭을 변수로 설정 후 읽어와서Spring Bean을 지정하는 방식
▶기술이 변하더라도application.yml의변수만 수정하는 방식으로소스코드는 변경하지 않아도되므로유지보수 용이
。팩토리 메서드 패턴으로 구현
▶ 중간에팩토리 클래스역할의Configuration Class를 정의 및 내부에서applications.yml에서 사전에 정의한값에 따라Spring Bean의등록여부를 결정하는@Bean Method정의
application.yml설정spring: application: name: Springpractice db: select: maridb▶
db.select에maridb이라는값부여
- `
팩토리클래스정의 후@ConditionalOnProperty를 통해의존성주입
。@Bean method를 포함하므로Configuration Class로 선언
。application.yml에 정의된변수값에 따라구현체를 자동으로 선택// PSA 인터페이스 interface jdbcConnector{ void getConnection(); } @Repository class OracleJdbcConnector implements jdbcConnector{ @Override public void getConnection(){} } @Repository class SQLiteJdbcConnector implements jdbcConnector{ @Override public void getConnection(){} } @Repository class MariaDBJdbcConnector implements jdbcConnector{ @Override public void getConnection(){} } @Configuration class DBConnectorSelectFactory { @Bean @ConditionalOnProperty(prefix = "db", name = "select", havingValue = "maridb") public MariaDBJdbcConnector mariaDBJdbcConnector(){ return new MariaDBJdbcConnector(); } @Bean @ConditionalOnProperty(prefix = "db", name = "select", havingValue = "sqlite") public SQLiteJdbcConnector sQLiteJdbcConnector(){ return new SQLiteJdbcConnector(); } @Bean @ConditionalOnProperty(prefix = "db", name = "select", havingValue = "oracle") public OracleJdbcConnector oracleJdbcConnector(){ return new OracleJdbcConnector(); } } @Service public class DbClient { private final jdbcConnector connector; public DbClient(jdbcConnector connector){ this.connector = connector; } }。
팩토리 클래스역할의DBConnectorSelectFactory에 의해application.yml에 설정된 값 (db.select=maridb)에 따라서DbClient의connector에는MariaDBJdbcConnector이 주입됨.
▶application.yml의db.select값에 따라서 해당하는Spring Bean이 등록되어의존성 주입
▶서비스가 변경 시소스코드자체의 변경없이 오직application.yml만 수정번외 :
팩토리클래스정의 후@Value를 통해의존성주입
。SpringEL방식을 통해applications.properties에 정의된변수값을 읽어와서 해당 값을 기반으로Spring Bean을 등록하여 주입
▶@Value를 선언한field는final을 선언하지 못하므로 중간에 읽어온 값을null로 설정하면 작동을 못하므로@ConditionalOnProperty방식에 비해 비추천@Configuration public class DBConnectorSelectFactory { @Value("${db.select}") private String select; public DBConnector dbConnector() { // select = null; 살인코드 if ("maridb".equalsIgnoreCase(select)) { return new MariaDBJdbcConnector(); } else if ("sqlite".equalsIgnoreCase(select)) { return new SQLiteJdbcConnector(); } else { throw new IllegalArgumentException("Unknown DB select value: " + select); } } }