Squirrel Dependency Injection -> Sqoin

Started by PSL, Today at 03:07 AM

Previous topic - Next topic

PSL

This Squirrel code implements a simple dependency injection (DI) framework with the following core features:

1. Provides a Module class for defining dependency bindings, supporting two binding types: single (singleton) and factory (factory). Register types and corresponding providers through the single() and factory() methods respectively.
2. Sqoin serves as the framework entry point and provides singleton context access (getInstance()), module loading (loadModules()), and instance acquisition (get()) functions through static methods.
3. SqoinContext is the core of the context, responsible for managing modules, storing singleton and factory registration information, and obtaining instances by type through the get() method (the singleton is created and cached by the provider when it is first obtained, and the factory creates a new instance each time it is called)

The overall dependency registration and resolution mechanism is implemented, which can be used to manage object dependencies in the server.

class Logger {
    function log(message) {
        print("Log: " + message);
    }
}

class UserRepository {
    logger = null;

    constructor(logger) {
        this.logger = logger;
    }

    function getUser(id) {
        this.logger.log("Getting user with id: " + id);
        return {
            id = id,
            name = "User " + id
        };
    }
}

class UserService {
    repo = null;
    logger = null;

    constructor(repo, logger) {
        this.repo = repo;
        this.logger = logger;
    }

    function getUserInfo(id) {
        this.logger.log("Fetching user info");
        return this.repo.getUser(id);
    }
}

This Squirrel code defines three business classes, forming a simple user information processing chain:
1. Logger class: Provides basic logging functionality, printing messages via the log method.
2. UserRepository class: Responsible for acquiring user data. Its constructor relies on Logger. It uses the getUser method to query user information based on ID and log it.
3. UserService class: Serves as the business service layer, relying on UserRepository and Logger. It encapsulates the user information acquisition process via the getUserInfo method, logging before calling repository layer methods.

The three collaborate through dependencies, embodying the layered design concept (log component, data access layer, business service layer), and can be combined with the dependency injection framework to manage dependencies.

local userModule = Module(function(module) {
        module.single("Logger", function(sqoin) {
            return Logger();
        });

        module.single("UserRepository", function(sqoin) {
            return UserRepository(sqoin.get("Logger"));
        });

        module.factory("UserService", function(sqoin) {
            return UserService(sqoin.get("UserRepository"), sqoin.get("Logger"));
        });
    });

This Squirrel code defines a dependency injection module userModule, which is used to configure the instance creation rules of each business class:
1. The configuration function is passed to the Module constructor, and the Logger and UserRepository are registered as singletons using the single method:
    1. The Logger instance is created directly using the no-argument constructor.
    2. The UserRepository depends on the Logger and constructs itself after obtaining the Logger instance through the dependency injection container.
2. The UserService is registered as a factory using the factory method: Each time a new UserService is constructed, the container retrieves the UserRepository and Logger instances.

This module declares dependencies and provides instance creation and dependency management rules for the previously defined Logger, UserRepository, and UserService, allowing them to be loaded and used by the injection framework.

local modules = [userModule];

Sqoin.loadModules(modules);

local userService1 = Sqoin.get("UserService");
local userService2 = Sqoin.get("UserService");

userService1.getUserInfo(123);
userService2.getUserInfo(456);

This Squirrel code example demonstrates the use of the dependency injection framework:
1. First, the previously defined userModule is added to the modules array. Sqoin.loadModules is used to load the module, completing dependency registration.
2. Sqoin.get("UserService") is called twice to obtain service instances (because UserService is registered as a factory, two different instances are generated).
3. The getUserInfo method is called on each UserService instance, passing in different IDs (123 and 456) to retrieve user information.

Running this code triggers logging and user data querying, demonstrating how the dependency injection framework manages object creation and dependency propagation. It also demonstrates the factory pattern's characteristic of generating a new instance each time a request is made.


PSL

https://github.com/leitingzi/vcmp_server.git
My repository contains the core code for dependency injection and a test server that can be run.

PSL

Currently implementing a flexible injection method of default parameters + dynamic parameters.

When registering a dependency, you can set a set of default configurations (for example, presetting the default output format for a logging tool); and when using the dependency, you can pass in new parameters at any time (for example, temporarily changing the output format to debug mode).
Dynamically passed parameters automatically override the default configuration, preserving the default settings while also adapting to temporary requirements in different scenarios. For example, you might use a regular database when registering a user service, but dynamically pass parameters to a test database during a subsequent call without having to modify the original registration rules. This provides flexibility and convenience.

module.factory("Logger", function(sqoin, args) {
return Logger(args.id);
}, {
id = 1
});

local logger1 = Sqoin.get("Logger");
local logger2 = Sqoin.get("Logger", {
id = 2
});

This code demonstrates the "default parameter + dynamic parameter" injection effect in the factory pattern:
1. When registering a Logger as a factory, default parameters are set via {id = 1}, serving as the base configuration for instance creation.
2. When logger1 is first retrieved without parameters, a Logger instance is created using the default parameters id = 1.
3. When logger2 is retrieved a second time with {id = 2}, the dynamic parameters override the default values, creating a new instance with id = 2.

Because of the factory pattern, each get call generates a new instance, and dynamic parameters take effect in real time, making it suitable for scenarios requiring differently configured instances.

PSL

If class B extends Sqoin (i.e. class B inherits from class Sqoin), then inside class B you can directly use this.get(...) to get all injected modules

class B extends Sqoin {
constructor() {
local logger = get("Logger");
logger.log("B");
}
}

This usage, achieved by "inheriting the Sqoin class," is equivalent to giving Class B a direct access to the dependency repository. Class B automatically inherits all of Sqoin's dependency management capabilities, such as obtaining singletons and factory instances, and passing dynamic parameters (this.get("UserService", {id = 1})). It's like moving the dependency injection "toolbox" directly into Class B, making it more convenient to use.