# SDK Factory Design When an application owner needs to configure the opentelemetry SDK, this can be done in different ways: - by assembly of available SDK elements already provided, - by extension of the SDK to support more functionality. The sections below investigate each use case, and the consequences when using shared libraries. Last, a section discuss the impact of C++ conditional parameters on the SDK interface, and how this affects shared libraries. ## SDK assembly Assume the application owner needs to build a trace provider, using the gRPC trace exporter. ### Case study, direct call to the SDK implementation classes A possible way to build the exporter is to call, from the application code: ```cpp opentelemetry::exporter::otlp::OtlpGrpcExporterOptions opts; auto exporter = std::unique_ptr( new opentelemetry::exporter::otlp::OtlpGrpcExporter(opts)); ``` When the application code is built and linked against static opentelemetry-cpp libraries, the result is a binary that is consistent, and works. However, in case of a bug fixed in opentelemetry-cpp itself, the entire application must be rebuilt (with a fixed version), redistributed, and reinstalled, to have the bug effectively fixed. This is highly undesirable. Instead of static libraries, now consider the same application built and linked against shared (dynamic) libraries instead. The desired goal with shared libraries is to allow to: - fix a bug in opentelemetry-cpp - deploy a fixed shared library - keep the application unchanged. For this to be possible, the ABI exposed by the SDK shared library must be stable. Here, class OtlpGrpcExporter is the implementation itself, and is very likely to change, even for a bug fix, so it is not a good thing to expose it. Setting the constraint on the SDK implementation that class OtlpGrpcExporter will never change is unrealistic, because a bug fix might require new members (for example, to add a mutex to fix a race), which will change the ABI (the memory layout is different), breaking the application code compiled with a different version. In summary, this line of code alone, in the application space: new opentelemetry::exporter::otlp::OtlpGrpcExporter(opts); prevents in practice any deployment using shared libraries. ### Case study, using Factory and builders from the SDK The SDK also provide Factory classes, that can be used as follows from the application code: ```cpp opentelemetry::exporter::otlp::OtlpGrpcExporterOptions opts; auto exporter = opentelemetry::exporter::otlp::OtlpGrpcExporterFactory::Create(opts); ``` While the application code does not change much, the amount of SDK internals exposed to the application is reduced significantly. OtlpGrpcExporterFactory::Create() actually returns a abstract SpanExporter object, instead of a concrete OtlpGrpcExporter object. As a result, the application binary is not even aware of the implementation class OtlpGrpcExporter. This property makes it possible to: - implement changes in the SDK itself - deploy a new SDK shared library - keep the application unchanged ### Case study, using Factory and shared gRPC client between OTLP gRPC exporters To reduce the cost of gRPC, the SDK allow users to share gRPC clients between OTLP gRPC exporters when these exporters have the same settings. This can be used as follows from the application code: ```cpp // Include following headers #include "opentelemetry/exporters/otlp/otlp_grpc_client_factory.h" #include "opentelemetry/exporters/otlp/otlp_grpc_exporter_factory.h" #include "opentelemetry/exporters/otlp/otlp_grpc_exporter_options.h" #include "opentelemetry/exporters/otlp/otlp_grpc_log_record_exporter_factory.h" #include "opentelemetry/exporters/otlp/otlp_grpc_log_record_exporter_options.h" // Create exporters with shared gRPC Client namespace otlp = opentelemetry::exporter::otlp; void SetupOtlp() { otlp::OtlpGrpcClientOptions client_opts; otlp::OtlpGrpcExporterOptions trace_opts; otlp::OtlpGrpcLogRecordExporterOptions log_opts; // Setting client_opts, trace_opts and log_opts // client_opts.endpoint = "localhost:1234"; // Or we can use client_opts = trace_opts; to copy options from environment of // trace OTLP exporter. std::shared_ptr shared_client = otlp::OtlpGrpcClientFactory::Create(client_opts); // Create exporters auto trace_exporter = otlp::OtlpGrpcExporterFactory::Create(trace_opts, shared_client); auto log_exporter = otlp::OtlpGrpcLogRecordExporterFactory::Create(log_opts, shared_client); // Other initialization codes ... } ``` Be careful, create OTLP exporters with an existing `OtlpGrpcClient` will ignore the options of gRPC when passing the `OtlpGrpcExporterOptions` or other option object. ## SDK extension Applications owners who want to extend existing SDK classes are expected to have a stronger dependency on the SDK internals. For example, with ```cpp class MyFancyOtlpGrpcExporter : public OtlpGrpcExporter {...} ``` the build depends on the actual SDK implementation. Class OtlpGrpcExporter is visible in the SDK public header files, to allow this pattern. Using shared libraries in this case is not recommended, because the SDK implementation is at greater risk of change. ## Conditional parameters Assume Foo() is part of the SDK, delivered as a shared library. When an API contains conditional parameters, as in: ```cpp void Foo(int x = 0, int y = 0); ``` this in reality produces 3 APIs usable by the application code: ```cpp void Foo(); void Foo(int x); void Foo(int x, int y); ``` as well as 1 ABI provided in a library: ```cpp void Foo(int x, int y); ``` The value of the defaults parameters (x = 0) is compiled in the application code. Assume that, for a bug fix, the API definition is changed to: ```cpp void Foo(int x = 0, int y = 1); ``` Deploying a new version of the SDK will have no effect, because the default value is in the application binary, not the shared library. Now, assume a later change needs to add a new parameter: ```cpp void Foo(int x = 0, int y = 1, int z = 2); ``` Here, clients will call the old Foo(int x, int y) ABI, crashing in the SDK because the SDK expects 3 parameters, not 2. Because of this, conditional parameters are to be avoided, not to be used in the SDK interface. Note that using conditional parameters in the opentelemetry-cpp API is ok, because the API is header only (there is no ABI).