문제

My apps commonly have one or more prop holders. These holders contain config data used in the app such as:

@Component
@ConfigurationProperties(prefix="app.orders")
@Data //lombok
public class OrderProps {

     private int maxItems;
     // other order props
}

Various components that use one or more of these props get injected with the prop holder:

@Service
public class OrderService {

    private int maxSize;

    public OrderService(OrderProps props) {
         maxSize = props.getMaxSize();
    }

 }

In this example, OrderService is only dependent on maxSize. Other classes may have also a varying number of properties needed from OrderProps.

Doesn't injecting @ConfigurationProperties classes violate the Law of Demeter. Specifically, doesn't it mask the real dependency (in this case an int) as described here?

Is there way to design without violating? Littering code with @Value("${order.maxItems}") or perhaps less problematic @Value("#{orderProps.maxItems}") seems to have its own set of problems especially when refactoring / maintenance.

도움이 되었습니까?

해결책

Doesn't injecting @ConfigurationProperties classes violate the Law of Demeter. Specifically, doesn't it mask the real dependency (in this case an int) as described here?

Yes and yes. If OrderService only requires an int, then that's all you should be injecting. OrderProps is a bag of properties that only exists as a convenient injection point for Spring, and you should never design your domain classes around a specific framework.

Littering code with @Value ... seems to have its own set of problems especially when refactoring / maintenance.

This is precisely why I'm not a fan of Spring component scan for larger projects. I find it cleaner and more maintainable to limit configuration and auto-wiring responsibilities to @Configuration classes:

@Configuration
public class OrderServiceConfiguration {
    @Autowired
    private OrderProps orderProps;

    @Bean
    public OrderService orderService() {
      return new OrderService(orderProps.getMaxItems());
    }
}

다른 팁

I think this is a better explanation of the Law of Demeter. Simply injecting the properties as follows doesn't violate that law as you are not reaching through OrderProps and don't need to know about it's internal structure.

@Service
@Builder
public class OrderService {
  private final OrderProps props;

  public void doSomething() {
    int maxSize = props.getMaxSize();
    ...
  }
}
  • Note you don't need code for a constructor here because Lombok will create it for you and Spring will invoke constructor injection if there is only one constructor.
  • I use @Builder as I prefer calling the builder from unit tests but @AllArgsConstructor would also work.
  • It is a good idea to make your service instance variables final as a service should be stateless anyway.

Not relying on framework-specific context or properties in your domain layer (assuming this is your domain layer) is a completely different architectural matter. An alternative to casablanca's answer is to abstract the configuration into an interface, and implement said interface in your application. This will allow you to change the configuration in real-time if need be, and not require you to fix your constructor every time another property is added (or even write a constructor to begin with, as explained above).

@Service
@Builder
public class OrderService {
  private final OrderConfig config;

  public void doSomething() {
    int maxSize = config.getMaxSize();
    ...
  }
}

public interface OrderConfig {
  int getMaxSize();
}

Example class in your application implementing the config interface.

@Service
@Builder
public class MyAppOrderConfig implements OrderConfig {
  private final OrderProps orderProps;

  @Override
  public int getMaxSize() {
    return orderProps.getMaxSize();
  }
}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 softwareengineering.stackexchange
scroll top