spring-projects / spring-ai

An Application Framework for AI Engineering
https://docs.spring.io/spring-ai/reference/index.html
Apache License 2.0
3.34k stars 861 forks source link

Consistent design of Options builder classes #1592

Open markpollack opened 1 month ago

markpollack commented 1 month ago

In some cases we have a dedicated top level object as a builder and in some cases it in an inner class. Also, most options builders do not implement an interface. We should adhere to the design that has been used pretty consistently in spring-framework which has the builder as an inner object and also an interface for the builder.

We should also add tests for the builders.

This came out of the PR discussion #414

youngmoneee commented 1 month ago

I still think that ensuring the immutability of objects is crucial to prevent potential concurrency issues. The reason I initially designed the builder as a separate object outside the option object was not only to facilitate simple inheritance but also to allow future extensions such as fromDefaults() and fromConfig().

However, I now think that separation of concerns and consistency are more important. Based on this, my current considerations are as follows:

  1. Operate in a consistent manner with other Spring projects through a simple builder.
  2. The object itself must be immutable, and modifications should only be allowed through the builder.
  3. Minimize inconvenience from this approach (e.g., by passing existing objects as builder parameters).
  4. A separate factory or converter that handles the responsibility of object creation and transformation when relying on Spring Boot.
  5. Testing for the implementation of the above points.

Do you have any additional ideas or considerations?

markpollack commented 1 month ago

Hi!

I agree with these points

  1. Operate in a consistent manner with other Spring projects through a simple builder.
  2. The object itself must be immutable, and modifications should only be allowed through the builder.
  3. Minimize inconvenience from this approach (e.g., by passing existing objects as builder parameters).

As for 4. I want to avoid the code duplication (which is quite large and boilerplate, prone to mistakes) between the 'core options' class and what is exposed to boot. We have figured out a way around that, basically anything that requires a @NestedConfigurationProperty annotation will be added to the file META-INF/additional-spring-configuration-metadata.json in the appropriate autoconfig package. See here, for the docs.

I didn't understand the comment

The reason I initially designed the builder as a separate object outside the option object was not only to facilitate simple inheritance but also to allow future extensions such as fromDefaults() and fromConfig().

but perhaps it isn't relevant if we follow the 'simple builder' approach in other spring projects.

One thing that I'm not sure about is to have an interface for the builder. It is good design, but does feel like a bit overkill.

Thoughts? Thanks for your feedback and interest!

youngmoneee commented 4 weeks ago

@markpollack

Thank you for your input! The link you provided is currently inaccessible 😞

I believe that directly applying the Lombok project or using an annotation processor could significantly reduce boilerplate code.

The fromConfig and fromDefault methods initially conceived deviate from the single responsibility principle. For these functionalities, it would be better to separate them into a new factory instead.

I agree with your suggestions 😊

tzolov commented 3 weeks ago

Related to the Builder: We should use a consistent convention for the Builder set methods. So far wit have many builders that use the with prefix (e.g. withModel(...)) and builders that don't use this prefix (e.g. model(...)). It looks like the Spring way is not to have a prefix. I will submit a PR that marks the withProperty(...) methods as deprecated and will add the new property(...) one. And perhaps after one release we can remove the deprecated builder methods ?