Example #1
0
    def test_configure_depedencies(self):
        ref1 = "AAA"
        ref2 = "BBB"
        refs = References.from_tuples(
            "Reference1", ref1,
            Descriptor("pip-services-commons", "reference", "object", "ref2",
                       "1.0"), ref2)

        config = ConfigParams.from_tuples(
            "dependencies.ref1", "Reference1", "dependencies.ref2",
            "pip-services-commons:reference:*:*:*", "dependencies.ref3", None)

        resolver = DependencyResolver(config)
        resolver.set_references(refs)

        assert ref1 == resolver.get_one_required("ref1")
        assert ref2 == resolver.get_one_required("ref2")
        assert None == resolver.get_one_optional("ref3")
class DirectClient(IConfigurable, IReferenceable, IOpenable):
    """
    Abstract client that calls controller directly in the same memory space. It is used when multiple microservices are deployed in a single container (monolyth) and communication between them can be done by direct calls rather then through the network.

    ### Configuration parameters ###

    - dependencies:
        - controller:            override controller descriptor

    ### References ###

    - *:logger:*:*:1.0         (optional) ILogger components to pass log messages
    - *:counters:*:*:1.0         (optional) ICounters components to pass collected measurements
    - *:controller:*:*:1.0     controller to call business methods

    Example:
        class MyDirectClient(DirectClient, IMyClient):
            def __init__(self):
                super(MyDirectClient, self).__init__()
                self._dependencyResolver.put('controller', Descriptor("mygroup", "controller", "*", "*", "*"))

            ...

            def get_data(self, correlation_id, id):
                timing = self.instrument(correlationId, 'myclient.get_data')
                result = self._controller.get_data(correlationId, id)
                timing.end_timing()
                return result

            client = MyDirectClient()
            client.set_references(References.from_tuples(Descriptor("mygroup","controller","default","default","1.0"), controller))
            data = client.get_data("123", "1")
            ...
    """
    _controller = None
    _opened = True
    _logger = None
    _counters = None
    _dependency_resolver = None

    def __init__(self):
        """
        Creates a new instance of the client.
        """
        self._logger = CompositeLogger()
        self._counters = CompositeCounters()
        self._dependency_resolver = DependencyResolver()
        self._dependency_resolver.put('controller', 'none')

    def configure(self, config):
        """
        Configures component by passing configuration parameters.

        :param config: configuration parameters to be set.
        """
        self._dependency_resolver.configure(config)

    def set_references(self, references):
        """
        Sets references to dependent components.

        :param references: references to locate the component dependencies.
        """
        self._logger.set_references(references)
        self._counters.set_references(references)
        self._dependency_resolver.set_references(references)
        self._controller = self._dependency_resolver.get_one_required('controller')

    def _instrument(self, correlation_id, name):
        """
        Adds instrumentation to log calls and measure call time. It returns a Timing object that is used to end the time measurement.

        :param correlation_id: (optional) transaction id to trace execution through call chain.

        :param name: a method name.

        :return: Timing object to end the time measurement.
        """
        self._logger.trace(correlation_id, f"Executing {name} method")
        return self._counters.begin_timing(f"{name} .call_time")

    def _instrument_error(self, correlation_id, name, err, result, callback):
        """
        Adds instrumentation to error handling.

        :param correlation_id: (optional) transaction id to trace execution through call chain.
        :param name: a method name.
        :param err: an occured error
        :param result: (optional) an execution result
        :param callback: (optional) an execution callback
        """
        if err is not None:
            self._logger.error(correlation_id, err, f'Failed to call {name} method')
            self._counters.increment_one(f"{name}.call_errors")
        if callback:
            callback(err, result)

    def is_opened(self):
        """
        Checks if the component is opened.

        :return: true if the component has been opened and false otherwise.
        """
        return self._opened

    def open(self, correlation_id):
        """
        Opens the component.

        :param correlation_id: (optional) transaction id to trace execution through call chain.
        """
        if self._opened:
            return

        if self._controller is None:
            raise ConnectionException(correlation_id, 'NO_CONTROLLER', 'Controller references is missing')

        self._opened = True
        self._logger.info(correlation_id, 'Opened direct client')

    def close(self, correlation_id):
        """
        Closes component and frees used resources.

        :param correlation_id: (optional) transaction id to trace execution through call chain.
        """
        if self._opened:
            self._logger.info(correlation_id, 'Closed direct client')

        self._opened = False