0

I have a Car object that has several properties. Each of its properties are populated using a service (generally one property per service). Each of those services generally call a 3rd party web service (e.g. carClient) to get its data. Most of my services also have logic on how to populate its Car object field. For example:

@Service
@RequiredArgsConstructor
public class CarPriceService  {

    // client of a 3rd party web service interface
    // I don't have control over this interface.
    private final CarClient carClient;   
    
    public void setAutoPrice(Set<Car> cars) {  
        
        // in this case, only one call to the web service
        // is needed. In some cases, I need to make two calls
        // to get the data needed to set a Car property.
        Map<String, BigDecimal> carPriceById = 
                carClient.getCarPrice(cars.stream().map(c->c.getId()).collect(Collector.toSet()));
                
        for (Car car : cars) {            
            // in this case the poulating logic is simple
            // but for other properties it's more complex
            BigDecimal autoPrice = autoPriceById.get(car.getId());          
            car.setAutoPrice(autoPrice);
        }
    }
    
}

The order of populating the Car properties is sometimes important. For example, CarValueService sets car.value using car.condition which is set by CarConditionService.

Is there a design pattern that works for handling the gradual build of an object over services? I'm aware of the Builder pattern but not sure how it would apply here.

user8297969
  • 415
  • 1
  • 6
  • 18

1 Answers1

0

Some kind of Pipeline Pattern1, 2 variant comes to mind. For instance,

final class Test {
    public static void main(String[] args) {
        final Car car = new Car();

        CarTransformer.of(c -> System.out.println("Install wheels!"))
            .then(c -> System.out.println("Install engine!"))
            .then(c -> System.out.println("Paint!"))
            .transform(car);
    }

    static final class Car {}

    static interface CarTransformer {
        default CarTransformer then(final CarTransformer step) {
            return (car) ->  {
                this.transform(car);
                step.transform(car);
            };
        }

        static CarTransformer of(final CarTransformer step) {
            return step;
        }

        void transform(Car car);
    }    
}

Obviously you probably wouldn't inline all transformations, but you get the idea. Here we use function composition to create the pipeline, but you could also just store transformations in a list.

Furthermore, if building the transformation pipeline is complex, you could could add the builder pattern in the mix. E.g.

final class Test {
    public static void main(String[] args) {
        final Car car = new CarBuilder()
            .installEngine("V8")
            .installWheel("front-left")
            .installWheel("rear-right")
            .paint("metallic blue")
            .build();
    }

    static final class Car {}

    static interface CarTransformer {
        default CarTransformer then(final CarTransformer step) {
            return (car) ->  {
                this.transform(car);
                step.transform(car);
            };
        }

        static CarTransformer of(final CarTransformer step) {
            return step;
        }

        void transform(Car car);
    }

    static final class CarBuilder {
        private CarTransformer transformer;

        CarBuilder() {
            transformer = CarTransformer.of(c -> {});
        }

        CarBuilder paint(final String color) {
            return then(c -> System.out.println("Paint in " + color));
        }

        CarBuilder installWheel(final String wheel) {
            return then(c -> System.out.println("Install " + wheel + " wheel!"));
        }

        CarBuilder installEngine(final String engine) {
            return then(c -> System.out.println("Install " + engine + " engine!"));
        }

        private CarBuilder then(final CarTransformer transformer) {
            this.transformer = this.transformer.then(transformer);
            return this;
        }

        Car build() {
            final Car car = new Car();
            transformer.transform(car);
            return car;
        }
    }
}

  1. Pipeline design pattern implementation
  2. https://java-design-patterns.com/patterns/pipeline/
plalx
  • 42,889
  • 6
  • 74
  • 90