Spring 기능 - PSA ( Portable Service Abstraction )

이정수·2025년 10월 17일

Spring

목록 보기
9/18

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이 존재 시 사용하는 방법

gradleConfiguration file ( application.yml or application.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.selectmaridb이라는 부여

  • `팩토리클래스 정의 후 @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 )에 따라서 DbClientconnector에는 MariaDBJdbcConnector이 주입됨.
application.ymldb.select 값에 따라서 해당하는 Spring Bean이 등록되어 의존성 주입

서비스가 변경 시 소스코드 자체의 변경없이 오직 application.yml만 수정

번외 : 팩토리클래스 정의 후 @Value를 통해 의존성 주입
SpringEL 방식을 통해 applications.properties에 정의된 변수값을 읽어와서 해당 값을 기반으로 Spring Bean을 등록하여 주입
@Value를 선언한 fieldfinal을 선언하지 못하므로 중간에 읽어온 값을 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);
 		}
 	}
 }
profile
공부기록 블로그

0개의 댓글