Link Search Menu Expand Document

Best Practices - SDK

Dec 7 2023 at 12:00 AM

  1. Best Practice for SDK Driver Configuration
    1. SOLID Principles
    2. Composition Root pattern
    3. Applying these principles to the Raptor 4 Framework:

Best Practice for SDK Driver Configuration

SOLID Principles

Adhere to Object Orientated Design

  1. Single Responsibility Principles
    • A class should only have a single responsibility (one function).
  2. Open/Closed Principle
    • A class should be Open for extension and Closed for modification.
    • It should be easy to add new functionality to a class without modifying the class itself.
  3. Liskov Substitution Principle
    • Objects in a program should be replaceable with instances of their subtypes.
    • Every subclass or derived class should be substitutable for their base or parent class.
  4. Interface Segregation Principle
    • A client should never be forced to implement an interface that they will not use.
    • Clients should also not be forced to depend on methods they do not use.
    • Many client-specific interfaces are better than one general-purpose interface.
  5. Dependency Inversion Principle
    • Entities must depend on abstract interfaces, not concrete classes.

Composition Root pattern

In the Composition Root Pattern, all dependencies are defined, constructed and injected into the container as soon as possible. This means that on request, the dependency chain is 100% pre-calculated and, in some cases, pre-constructed.

This pattern adheres to the following principles:

  1. Single location for object construction
    • The entire dependency graph is discoverable in one location in the code.
  2. Build a lean container
    • The composition project is a map of how your dependencies are constructed, how they interact, and how they are resolved.
  3. As close to Init or Entry Point as possible
    • As soon as we have an active process, we want to construct the container.
  4. Pass configuration to the container for object construction
    • The container has all the configuration necessary to build ALL dependencies.
  5. Predictable dependency graph
    • All calls to the project should have a pre-constructed, pre-discovered dependency chain. No dependencies are built on the fly.

Applying these principles to the Raptor 4 Framework:

There are two types of Assemblies in the Framework:

  1. Abstraction assemblies
    • These Assemblies contain only abstraction classes and components (i.e. Interfaces and Enumerations). They do not contain any concrete implementation classes (unless in very exceptional circumstances).
    • They have dependencies on Abstraction assemblies only, never Concrete assemblies.
  2. Concrete assemblies
    • These assemblies contain the concrete implementations of the associated abstract assembly.
    • They provide a static extension method on the interface Microsoft.Extensions.Hosting.IHostBuilder that will load the concrete classes and configuration into DI.
    • They have dependencies on Abstraction assemblies only, never Concrete assemblies.
    • All configuration is done via JSON text files that are added to DI in a way that keeps track of on-the-fly changes, and options are injected into the classes that need them.

The application and its dependencies are brought together and configured in the ASP.Net Core Service assembly’s Program.cs file as in the example below:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .AddRaptorEnvironment()
            .AddRaptorBootstrap()
            .AddAsyncLockExecutor()
            .AddPrometheus()
            .AddRaptorDataProtection()
            .AddRaptorHealth()
            ...
            .ConfigureWebHostDefaults(builder => {
                builder.UseStartup<Startup>();
            });
}

Avoid adding dependencies using the Startup.cs file. All local service dependencies should be added using the ConfigureServices method.

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
        ...
        .ConfigureServices((context, services) => {
            //Add your dependencies here
        })

        ...
}