Skip to main content

A microservice-based architecture

To understand the software mechanics throughout these docs, we will start by outlining the chosen software architecture and the rationale behind it. This will make it easier to understand the several software components later on.

At ASE, we have chosen for a microservice-based architecture. In a nutshell, this means that there is no single monolithic binary or process that runs all tasks, but rather multiple (smaller) processes that tackle a subtask and communicate with each other to solve the big task at hand.

Definition

A microservice-based architecture is a modular architecture. We will use these terms interchangeably.

To get a better feeling for the implications, pros and cons of a microservice-based architecture, we will introduce a basic running example that will tie all theoretical aspects of the software to a real use case. In this example, the main task will be for the Rover1 to stay between the outer edges of a track and drive forward autonomously, comparable to a lane assist system in modern cars.

running example main task

The Rover1 should calculate its position based on the input of one sensor: a camera attached to the front of the car. It should compute how far of the ideal position it is and should steer accordingly (i.e. if it approaches the left edge, steer right, and if it approaches the right edge, steer left) to avoid running of the track. Speed will be constant.

note

It is assumed that the ideal position for the Rover1 is on the middle of the track in this example. This is not necessarily always the case (think about the ideal racing line for F1 cars) but will serve the purpose of explaining the software architecture.

To solve this main task, some subtasks might already come to mind. Let's consider a very simple modular software stack that can be used to tackle the main task on the Rover1:

basic driving pipeline

Definition

An example software stack as above is called a processing pipeline (or a pipeline in short). It is a fitting term since, like in a real pipeline, data flows uni-directionally to the end of the stack. We will use this term a lot throughout the documentation.

In this pipeline, we have decided to split the main task up into three subtasks. Let's sketch out their implementations and interfaces:

  • The Camera component will capture an image of the track and process it. It will convert the image it captured to a simpler structure that the controller can work with. For example, the image might be reduced to an array of (X,Y)-coordinates of the track's edges that the controller can use to determine if it is still in the middle.
  • The Controller component will take in data from the camera and determine what action should be taken based on the info it received. Should the car steer left? Steer right? Or is the current position good enough?
  • The Actuator will take the controller outputs and directly send signals to the motors and servo to move them. This is quite a 'dumb' component since it only makes sure the hardware does what the controller says.

These components will run as separate processes on the Rover1, as opposed to running a single process that will solve the task. To let the Rover1 drive autonomously, one would start the camera component first, then the controller component and finally the actuator. Below, important benefits and drawbacks of this approach are highlighted.

Benefits of a modular software architecture

  • Flexibility: since the contract between components is the only spec that should be adhered to, developers are given great flexibility in how they implement their components. For example, you might implement your Camera component in Python because it has some nice libraries, and your Actuator in C++ because efficient low-level controls are preferred. You might even find yourself developing hybrid pipelines where some components run in the cloud, while others run locally on the car.
  • Robustness and recoverability: since each component is isolated, the error boundary will be isolated as well. Suppose that your Camera component crashes unexpectedly, the Controller and Actuator can still keep running safely (and preferably bring the car to a safe stop). The Camera component can be restarted. A crash in one component will not crash the entire system.
  • Testability: you can unit test individual components to confirm that they behave as expected. You can also swap in a different version of a component for A/B testing by just running a different process.
  • Easier to simulate: you can swap out a "real" software component and replace it by a "mock" part to simulate specific scenarios, by just running a different process.
  • Easy cooperation: when developing a complex pipeline with your team, you just have to define the "contracts" during the software design phase. Everyone can implement their own component which holds up to their end of the contract. Integration should be smooth.

Drawbacks of a modular software architecture

  • Increased complexity: since your software is "split up" among multiple separate components, complexity will increase. Your code is just more distributed than it would be in a monolith software stack and you might need to explore ways to keep all code DRY (Don't Repeat Yourself).
  • Need for formal contracts: contracts need to be defined clearly and language agnostically. Communication protocols need to be clearly defined to actually make integration of multiple components a smooth experience.
  • Distributed configuration: how does a component know if the other component is running and reachable? How should runtime configurations be saved? These are unanswered questions in a microservice-based approach. A proper "Component Discovery" and "Config distribution" system has to be put in place when more components are being added.

At ASE, we have decided that the pros of a microservice-based approach outweigh the cons, especially considering that we expect students to "bring their own component" that will fit into the pipeline that we provide. We have also taken great care to develop solutions for the mentioned disadvantages, which we will explain throughout these docs.

Definition

In this example, we talked about software components that make a pipeline, but from now on, we will refer to such components as services (as is common in other literature). Our basic pipeline example would then consist of a Camera service, a Controller service and an Actuator service.

Definition

At ASE, we refer to all services that should run on the car as modules. You will most likely not find this term in existing literature, but it helps us keeping our services organized and is also reflected in the way we manage our software and repositories. We will use this term from now on.