0

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.

2 Answers 2

3

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());
    }
}
4
  • Thanks, but what impact do we have by losing @Service annotation on OrderService? Is it only component scanning?
    – James
    Commented Mar 5, 2019 at 18:58
  • Yes, @Service and other component annotations are just markers for component scanning (Spring javadoc).
    – casablanca
    Commented Mar 6, 2019 at 7:19
  • Thanks. This post and the Spring reference doc indicate that other benefits of using @Service is clearly marking the intent of the class, ease of targeting pointcuts, possible tool integration and that @Service may carry additional semantics in future releases. So, I suppose it's a trade-off between those benefits vs not violating the Law of Demeter.
    – James
    Commented Mar 6, 2019 at 15:56
  • Note that anything you can do with @Service, you can also do externally via a configuration class. The service annotation might be a tad bit simpler but the trade-off is that you couple your classes to Spring.
    – casablanca
    Commented Mar 7, 2019 at 5:31
2

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();
  }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.