Making an IOC container in Python
Part 3 The Service Container
10/11/2017
Service Container
A service container may be used to store all the services the program needs and provide a way to register each service and how to create them. This article shows a simplest service container and a simplest injector look like.
A simplest service container may look like the following:
class ServiceContainer(IServiceContainer):
def __init__(self):
self._type_container = []
def register_service(self, service: Type):
self._type_container.append(service)
def create_service(self, t: Type):
Service = next(ServiceType for ServiceType in self._type_container if ServiceType == t)
return Service()
The above code uses a list to store all the services, and when the create_service
method gets called, it takes the service from the list and creates the service.
Injector
As we mentioned before, an injector may be used to resolve the dependency a class needs and create an instance of the class.
Making an IOC container in Python
Part 4 Service Providers
10/11/2017
With the simplest service container we created in the previous article, now we can look at making the service container provide a way to customise service creation. To achieve this, service providers may be used.
Imagine how a MySqlDBConnection
service would look like:
class MySqlConnection:
def __init__(self, host, port, database):
...
We do not want the MySqlConnection
service to read the configuration by itself, as this part of code is not relating to what this service should provide.
Typically, we will need to read the host address, the port number, and the database from a configration file to instantiate the above service. We may introduce a service provider class to achieve this.
Service Provider
A provider class for the MySqlDBConnection
service should be:
class MySqlConnectionProvider(IServiceProvider):
def __init__(self, configuration: IConfiguration):
self._configuration = configuration
def provide(self):
return MySqlConnection(
self._configuration["db_host"],
self._configuration["db_port"],
Type Hinting in Python
7/22/2017
In Python, the types of variables may change during the execution of the program. It makes the code more flexable but it also confuses developers and the IDE sometimes, as both of them may need to analyse the code to find out the types of variables.
To solve this issue, Python introduced the Type Hinting feature. It allows developers declare the types of variables like what developers do in static-typed languages. For example:
def add(a: int, b: int):
return a + b
When calling the above function with some other types of parameters, IDE will be able to tell the developers it is not what the function expects. For example:
sum('a', 'bcd') # Developers will be warned
It also helps IDEs to provide suggestions. For example:
l = ['a', 'b', 'c', 'd']
def foo(arr):
for i in arr:
i. # type of i is unknown, the IDE is not able to provide any suggestions
# VS
Making an IOC container in Python
Part 2 Resolving Dependency
7/22/2017
The most essential part for resolving the dependency is retrieving the dependency declared by the __init__
. To achieve this, we may use the inspect module. This article will share how I managed to do this with this module in my past projects.
The inspection module's documentation can be found on this page.
Retrieving the types of parameters
We may use the signature
function to retrieve parameters and their types.
def test(a: int):
...
inspect.signature(test) # <Signature (a: int)>
parameters = signature.parameters # mappingproxy(OrderedDict([('a', <Parameter "a: int">)]))
The signature
function returns a Signature
object, from which we can access the parameter declaraction including the type hinting of each parameter by accessing it's parameters
property.
How we may do it looks like the following
class Test:
def __init__(self, a: IConfiguration):
Making an IOC container in Python
Part 1 Dependency Injection
7/21/2017
Class decoupling
Imagine there is a class UserRepository
which reads configuration using ConfigurationManager
, processes user-related actions, and stores user data in the database using MySqlConnection
.
class UserRepository:
sqlConnection: MySqlConnection
configurationManager: ConfigurationManager
def __init__(self):
self.sqlConnection = MySqlConnection("localhost", 3306, "username", "password", "utf8")
self.configurationManager = ConfigurationManager("/app.xml")
# ...
This class depends on ConfigurationManager
and MySqlConnection
, which implement services instead of interfaces. Therefore, if we were to switch the database from Mysql to SqlServer, it would cost developers a lot of effort to change the dependency. Also, a class should only care about how its implementation instead of the implementation of other services.
To decouple the above classes, we should make an ISqlConnection
interface for MySqlConnection
and an IConfigurationManager
interface for ConfigurationManager
and let both types inherit from the interfaces. Then, we can refactor UserRepository
to the following:
class UserRepository:
sqlConnection: ISqlConnection
configurationManager: IConfigurationManager
def __init__(self):
self.sqlConnection = ServiceFactory.createService(ISqlConnection)
self.configurationManager = ServiceFactory.createService(IConfigurationManager)