Skip to content

Library Philosophy

Johannes Schneider edited this page Jul 3, 2023 · 2 revisions

Modularity

This library is build with extensibility and modularity in mind. Therefore, it is possible to consume only parts of the library, depending on the use case at hand.

Core API

The most basic module, which is the bare minimum any application could use, is the java-core-api module. It contains the ServiceBinding interface, which is what this library is all about. Additionally, the module also ships a default implementation of the interface (the DefaultServiceBinding class), as well as some more fundamental classes.

Access API

The java-access-api module builds on top of the core API. It contains the ServiceBindingAccessor interface, which defines the API for retrieving ServiceBinding instances from the environment.

The most noteworthy implementation next to the ServiceBindingAccessor interface is the DefaultServiceBindingAccessor class. This class serves as a static, always accessible singleton-like entry point to retrieve a ServiceBindingAccessor instance. Under the hood, it uses the service locator pattern to find available implementations of the ServiceBindingAccessor interface on the class path and combines them using the ServiceBindingMerger. This way, it combines the results of all available SerivceBindingAccessor implementations, which is how multiple runtime environments can work together.

Applications and libraries are requested to use this implementation as a FALLBACK only.

Please find more guidance about best practices and recommendations in the Best Practices section.

Consumption API

The java-consumption-api module also builds on top of the core API. It provides type-safe abstractions over the ServiceBinding that can be used to read data from a given ServiceBinding in more complex scenarios.

Extensibility

The Service Binding Library comes with out-of-the-box support for Cloud Foundry and K8s (e.g. SAP Kyma) in combination with the SAP BTP Service Operator runtime environments.

However, the library is designed to be extensible, so that support for additional runtime environments can be added easily by adding new implementations of the ServiceBindingAccessor interface. These implementations should follow the same principals as the existing ones, which can be summarized as follows:

  • Be lenient. Anticipate that the implementation might be called for a runtime environment that it does not support. In this case, the implementation should return an empty ServiceBinding list. Refrain from throwing exceptions if possible.
  • Focus on one specific type of service binding per implementation.
  • Expose the implementation via the service locator pattern if it should be picked up by the DefaultServiceBindingAccessor ( and the ServiceBindingAccessor#getInstancesViaServiceLoader()) implementation by default.

Best Practices

This library is somewhat opinionated about the way it should be used. The following sections describe the best practices and recommendations for using the library.

Immutability

The ServiceBinding interface is an immutable abstraction. This means that the data contained in a ServiceBinding instance cannot be changed after it has been created.

As a consequence, operating on the same ServiceBinding instance for a longer period of time is not recommended as the contained data might be outdated (e.g. the service binding might have been rotated in the meantime). Instead, it is recommended to retrieve a fresh ServiceBinding instance from the ServiceBindingAccessor whenever the data is needed.

Caching

As explained in the section above, it is recommended to retrieve a fresh ServiceBinding instance from the ServiceBindingAccessor whenever the data is needed. However, this does not mean that the ServiceBindingAccessor itself cannot cache the results of its operations. In fact, the library even comes with the SimpleServiceBindingCache, which can be used to cache the results of a ServiceBindingAccessor instance for a fixed amount of time. By default (i.e. within the DefaultServiceBindingAccessor), this cache is used to store the results of the underlying ServiceBindingAccessor for 5 minutes. That way, expensive operations (such as reading from the file system) are significantly reduced.

Instances vs. Static State

This library focuses on instance-based state almost exclusively (the DefaultServiceBindingAccessor is the only exception). Doing so provides the following benefits:

  • It allows for more flexibility in terms of testing. For example, if APIs accept an instance of ServiceBinding instead of always loading them from the DefaultServiceBindingAccessor, they can be tested in isolation (i.e. without having to manipulate the static state of the DefaultServiceBindingAccessor).
  • It allows for more flexibility in terms of extensibility. If applications want to support a custom runtime, for example, they don't need to change any existing code of this library but, instead, can just add more code from their side. Everything else should just continue to work.

Composition over Inheritance

This library uses a lot of composition instead of inheritance, especially in the ServiceBindingAccessor related classes. This is done to allow for more flexibility in terms of extensibility and should be adopted by applications that want to extend the functionality of this library.