Investigation: The creation of a configuration data model
Rationale
As a developer it would facilitate my life if there are data models in the code-base which represent a particular configuration. So far in our code-base we have treated a configuration as a dictionary, therefore there was no standard way of interacting with the configuration. Having a data model would provide a standard way of interacting with a particular object, for example for validating the structure of a dictionary, parsing, and representing a configuration as a dictionary.
For example, if a CosmosNodeConfig object existed, the MonitorsManager could validate the Cosmos node configuration received from the ConfigsManager using the CosmosNodeConfig.validate() function, load the configuration inside the model using the CosmosNodeConfig.load_json function and retrieve the json representation of the data model using the CosmosNodeConfig.retrieve_json function. As a result, the developer would have a standard way of interacting with a particular configuration.
In the alerter service we already have something similar inside the src/configs folder. However, the folder structuring is not ideal. We should create a data_models/configs folder containing all the configurations' data model. NOTE: It is suggested that these data models are aligned with the configuration changes conducted in UI iteration 2.
For ticket closure
The aim of this task is to come up with a data model design for each configuration we are dealing with in the alerter. This design should ideally be submitted on confluence.
Having this design, we can then create a number of implementation tickets such as:
- Tickets which implement the respective models
- Tickets which enables the components to make use of the models
You could use this minimum working example to try and understand what type of data models we want to have. Note that the code below should be used only for example purposes, hence, the developer should think of the best way to implement this:
class Config(ABC):
def __init__(self):
self._parent_id = ''
@property
def parent_id(self) -> str:
return self._parent_id
@abstractmethod
def _is_valid(config: Dict) -> bool:
pass
def _parse(config: Dict) -> None:
self._parent_id = config['parent_id']
def load(config: Dict) -> bool:
if self._is_valid(config):
self._parse(config)
@abstractmethod
def to_json() -> Dict:
pass
class NodeConfig(ABC, Config):
def __init__(self):
super.__init__()
self._node_id = node_id
@property
def node_id(self) -> str:
return self._node_id
@abstractmethod
def _is_valid(config: Dict) -> bool:
pass
def _parse(config: Dict) -> None:
super()._parse(config)
self._node_id = config['node_id']
@abstractmethod
def to_json() -> Dict:
pass
class CosmosNodeConfig(NodeConfig):
def __init__(self):
super.__init__()
self._cosmos_rest_url = ''
@property
def cosmos_rest_url(self) -> str:
return self._cosmos_rest_url
def _is_valid(config: Dict) -> bool:
return schema.is_valid(config)
def _parse(config: Dict) -> None:
super()._parse(config)
self._cosmos_rest_url = config['cosmos_rest_url']
def to_json() -> Dict:
return {
'parent_id': self.parent_id,
'cosmos_rest_url': self.cosmos_rest_url,
'node_id': self.node_id,
}
Schema library we could use: https://pypi.org/project/schema/
@zimaah Also suggested whether we could use custom made types rather than the generic Dict type. After some investigation I have found this resource which makes use of a TypedDict : https://adamj.eu/tech/2021/05/10/python-type-hints-how-to-use-typeddict/
Therefore, apart from creating the Object Data Models/Interfaces, before-hand we need to also create the types which the Object Data Models interact with. These types can reside in the src/types folder. In the examples above we could have the following:
class ConfigDict(TypedDict):
parent_id: str
def _parse(config: ConfigDict) -> None:
super()._parse(config)
self._node_id = config['node_id']