ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 6. 그레이들 멀티프로젝트 - 데이터베이스 셋업 및 활용(2) - Functional Interface, dynamic bean selector
    스프링개발자/301 - 아키텍처 2020. 10. 7. 05:51

    [배경]

    세 가지 탈것의 종류가 있다; 자동차, 트럭, 자전거

    각각의 고객은 이 세가지의 탈 것 중에 하나를 고를 수 있다.

    자동차를 고른 고객은 car instance가 생성되어 프로그램에 사용되게 되고

    트럭을 고른 고객은 truck instance가 생성되어 프로그램이 진행된다.

    각각의 탈 것 instance에 따라서 속도, 무게, 부피 등등을 다르게 할 수 있다.

    이렇게 하면, code base change없이 데이터베이스 값 변경만으로 dynamic한 request call이 가능하다.

     

    1. 모델 클래스 준비

    2. 탈것들의 Bean Config 만들기

    3. 프로그램 시작시에 설정 불러오기

    4. 테스트


    1. 모델 클래스 준비

    자동차, 트럭, 자전거의 세 가지 탈것 모두 공통점이 있으니 탈 것이라는 인터페이스로 묶기

    public static create method로 interface를 통해서 instance 관리하자.

    package com.example.monorepo.vehicle;
    
    public interface Vehicle {
        boolean canLoadPassenger(int passengerConsumption);
    
        boolean canLoadGoods(int goodsConsumption);
    
        boolean canDrive();
    }
    
    package com.example.monorepo.vehicle;
    
    import lombok.Data;
    
    @Data
    public class Car implements Vehicle {
        private final int passengerCapacityLimit = 4; // person
        private final int goodsCapacityLimit = 100; // kg
        int passengerCapacityCurrent = 0;
        int goodsCapacityCurrent = 0;
    
        @Override
        public boolean canLoadPassenger(int passengerConsumption) {
            if (passengerCapacityCurrent + passengerConsumption > passengerCapacityLimit) return false;
            passengerCapacityCurrent += passengerConsumption;
            return true;
        }
    
        @Override
        public boolean canLoadGoods(int goodsConsumption) {
            if (goodsCapacityCurrent + goodsConsumption > goodsCapacityLimit) return false;
            goodsCapacityCurrent += goodsConsumption;
            return true;
        }
    
        @Override
        public boolean canDrive() {
            return (passengerCapacityCurrent * goodsCapacityCurrent > 250) ? false : true;
        }
    
        public static Car create(){
            return new Car();
        }
    }
    
    package com.example.monorepo.vehicle;
    
    import lombok.Data;
    
    @Data
    public class Truck implements Vehicle {
        private final int passengerCapacityLimit = 2; // person
        private final int goodsCapacityLimit = 500; // kg
        int passengerCapacityCurrent = 0;
        int goodsCapacityCurrent = 0;
    
        @Override
        public boolean canLoadPassenger(int passengerConsumption) {
            if (passengerCapacityCurrent + passengerConsumption > passengerCapacityLimit) return false;
            passengerCapacityCurrent += passengerConsumption;
            return true;
        }
    
        @Override
        public boolean canLoadGoods(int goodsConsumption) {
            if (goodsCapacityCurrent + goodsConsumption > goodsCapacityLimit) return false;
            goodsCapacityCurrent += goodsConsumption;
            return true;
        }
    
        @Override
        public boolean canDrive() {
            return (passengerCapacityCurrent * goodsCapacityCurrent > 950) ? false : true;
        }
    
        public static Truck create(){
            return new Truck();
        }
    }
    
    package com.example.monorepo.vehicle;
    
    import lombok.Data;
    
    @Data
    public class Bicycle implements Vehicle {
        private final int passengerCapacityLimit = 1; // person
        private final int goodsCapacityLimit = 20; // kg
        int passengerCapacityCurrent = 0;
        int goodsCapacityCurrent = 0;
    
        public boolean canLoadPassenger(int passengerConsumption) {
            if (passengerCapacityCurrent + passengerConsumption > passengerCapacityLimit) return false;
            passengerCapacityCurrent += passengerConsumption;
            return true;
        }
    
        public boolean canLoadGoods(int goodsConsumption) {
            if (goodsCapacityCurrent + goodsConsumption > goodsCapacityLimit) return false;
            goodsCapacityCurrent += goodsConsumption;
            return true;
        }
    
        public boolean canDrive() {
            return (passengerCapacityCurrent * goodsCapacityCurrent > 20) ? false : true;
        }
    
        public static Bicycle create(){
            return new Bicycle();
        }
    }
    

    2. 탈 것들 Bean Config 만들기

    Functional Interface를 통해서 Vehicle클래스를 리턴한다.

    Bean Config을 만들고, Primary설정

    @FunctionalInterface
    public interface VehicleManagerConfiguration {
    
        Vehicle getVehicle();
    }
    @Configuration
    public class VehicleManagerBeanConfig {
    
        @Primary
        @Bean("car")
        VehicleManagerConfiguration carManager() {
            return Car::create;
        }
    
        @Bean("truck")
        VehicleManagerConfiguration truckManager() {
            return Truck::create;
        }
    
        @Bean("bicycle")
        VehicleManagerConfiguration bicycleManager() {
            return Bicycle::create;
        }
    }

    Vehicle클래스가 아닌 VehicleManagerConfiguration을 리턴하는 이유는, Bean의 리턴타입이 functional interface이어야 하기 때문. Additional wrapper class이다.

     

     

    3. 프로그램 시작시에 설정 불러오기

    ApplicationContext를 이용하면, 어플리케이션이 동작될때에 추가 로직을 실행할 수 있다.

    여기에서는 데이터베이스 값(Client의 vehicle설정 값)을 불러와서 그 이름의 bean를 찾아 리턴하자.

    다시 한번, Vehicle instance 아닌 Vehicle Functional Interface를 리턴한다.

     

    @Component
    public class VehicleSelector {
    
        private final ApplicationContext applicationContext;
    
        public VehicleSelector(ApplicationContext applicationContext) {
            this.applicationContext = applicationContext;
        }
    
        public VehicleManagerConfiguration determineVehicleForClient(Client client) {
            Optional<String> vehicleBeanName = Optional.ofNullable(client.getMonoParameters().get(MonoParameter.VEHICLE));
            Map<String, VehicleManagerConfiguration> beansOfType = applicationContext.getBeansOfType(VehicleManagerConfiguration.class);
            return vehicleBeanName.map(x -> beansOfType.getOrDefault(x, applicationContext.getBean(VehicleManagerConfiguration.class)))
                    .orElse(applicationContext.getBean(VehicleManagerConfiguration.class));
        }
    }

     

    4. 테스트

    @Service
    public class VehicleService {
    
        @Autowired
        private VehicleSelector vehicleSelector;
    
        // ...
    
        public Vehicle getMyVehicle(Client client){
            return vehicleSelector.determineVehicleForClient(client).getVehicle();
        }
    }
    
    @RestController
    @RequestMapping(value = "/main")
    public class MainController {
    
    	// ...
        
        @Autowired
        ClientService clientService;
    
        @Autowired
        VehicleService vehicleService;
        
        // ...
    
        @GetMapping("/vehicle")
        public String getClientVehicle(@RequestParam("guid") String clientId){
            Client client = clientService.getClientById(clientId);
            return vehicleService.getMyVehicle(client).getClass().toString();
        }
    }

    브라우저 열고 다음의 값을 실행(GET)

    http://localhost:8080/main/vehicle?guid=cac6cea0-8620-4d0f-be62-dd24168a40c2

    class com.example.monorepo.vehicle.Car

     

    http://localhost:8080/main/vehicle?guid=3f99ea1c-6c62-46ca-8e25-6f8ac34bb222

    class com.example.monorepo.vehicle.Truck

     

    http://localhost:8080/main/vehicle?guid=ff6fb705-b37a-415f-bcd5-7088936938e5

    class com.example.monorepo.vehicle.Bicycle

     

    완성된 코드

    github.com/2ndPrince/monorepo/tree/database-2

Designed by Tistory.