Microservice Terminal Simulator

The Problem

One of the main characteristics of microservice is that communication occurs over the network. This has its benefits architecture wise, but it poses a challenge for developers. What would be a simple method call in a monolithic system suddenly needs networking code to be written and managed. I implemented generic GetData / UpdateData methods to minimize the amount of direct network calls. However, there are still direct calls needed that are not data based such as PutContainer(containerId) or DriveTo(targetpostion). Writing networking code for each of these methods for each service is a pain. That is why this week, I set out to improve the internal communication workflow.

There are several solutions that can be used to optimize the workflow. Grpc provides code generation. For each supported language gRPC generates client abstract server classes. This abstracts away the network calls in plain method calls. This is much nicer than having to write the clients and servers yourself. However, when creating the prototype in week one, I realized there are some issues and lacking functionality with the gRPC generated clients such as:

Previous solution

To combat some of these issues this, I initially abstracted away the service calls into static classes like so:

So at least I could use the domain models such as Location or ContainerLength in the service logic code. This is done for multiple services. Each implements the calls it needed. This is an improvement compared to using the grpc methods directly, but is still quite developer unfriendly. For each network call this repetitive and error prone code has to be written. There is still no easy way to discover the possible network calls without diving into the protobuffer definitions. Most of the problems in the last paragraph are not solved by this and the manual writing of each method is tedious.

C# Code Generation

I tried solving this by generating this code. C# provides source generators that can add code files during the build process. I am relatively new to this, but I wanted to try it out.

After some trying, I was able to generate this code:

This is generated for each service for each method. The generator generates this based on the protobuffer files. Enums are currently not implemented. They are substituted with integers. Arrays are not working. This still needs to be implemented. Apart from those two issues the code seems to work correctly.

Tests

I wrote unit tests for these functionalities. Using the Moq framework I test whether the generated code pass the correct parameters to the invoker and if the invoker calls the correct gRPC client methods.

I have not used the generated code in the services as of yet, because I need the array functionality to be able to completely migrate away from the manual gRPC code. Next week I will try to implement arrays and migrate my codebase to use the generated code.

Discussion

While writing this I realized that services should not call other services at all. Everything should go through the GetData and UpdateData calls. As with ECS designs systems do not call other systems. Most functionality could be rewritten to use the data. Such as Sc.DriveTo(target) becomes GetData<Sc>().Targetposition = target. Certain functionality such as the VisualizationBridge methods are more challenging to migrate. Taking a data driven design approach should improve the system interconnectedness. Next week I want to take a serious look at where to use and not use direct service to service calls.