Skip to content
Apollo Missionsservice

Apollo Missionsservice

Andi Lamprecht Andi Lamprecht ·· 9 min read· Accepted
ADR-0119 · Author: Sybil Melton · Date: 2025-02-07 · Products: uncrew
Originally ADR-0086-Apollo-MissionsService (v15) · Source on Confluence ↗

Missions Service

Context

Apollo’s backend is a collection of microservices and thus services organized around business capabilities (and not technology, e.g.: software stack layers). This architectural pattern is friendly to the Domain Driven Design, which asks to split the business domain into Bounded Contexts - touching, but not overlapping semantical sandboxes that hold consistent, domain sub-models described with unambiguous language (aka Ubiquitous Language). Examples of separate bounded contexts are the sales and support perspectives around some product or - taking to the Drone Delivery use-case - orders, scheduling, drone maintenance, predictive analysis and shipping. With the main challenge in identifying microservices, DDD dictates that a microservice mustn’t straddle more than one bounded context.

6db10754fa3797c9c865156655e9be74-shipping.png

Apollo’s scope seems blessed to cozily rest is just one bounded context. It has no orders or billing perspectives, no drone maintenance or packaging. Apollo is just a slim and tall flight control vertical interfacing those who need missions flown and flight controllers that can fly these missions. We have to be honest to note that Apollo’s clients understand missions differently than the UAVs do. For our clients a mission is a set (not even a list) of objectives, e.g.:

  • pick payload P at location A
  • pick payload Q at location B
  • deliver payload Q to location A
  • perform an orthomosaic survey of region C
  • deliver payload P to location D

Apollo then compiles this mission to decide whether the UAV assigned to the mission (or one it can find for the mission) can carry payloads P and Q at the same time, if not, it will have to conclude to:

  • go to B and pick the payload Q;
  • go to A, deliver Q and pick P;
  • go to D and deliver P
    And while doing all this it will have to run the traveling salesman algorithm to decide between which two locations it will cost it the smallest amount of energy to execute the orthomosaic at C. It will plot risk-minimizing paths between each two locations to work out how to traverse between them. Having done all this Apollo will hold a complex waypoint plan sprinkled with actions (e.g.: execute off-board precision landing sequence) that it can send to the UAV for execution.

These two perspective of a mission is a difference Apollo has to reconcile, consciously support and translate between. Let’s call the client’s perspective a Mission Request and the richer UAV perspective Mission Plan.

To continue the challenge of identifying microservices, DDD asks us to identify entities and aggregates within the model. Entities are things that have an identity and lifespan, so a Mission Plan is no doubt an entity, but a UAV position/telemetry sample is not. Aggregates are consistency boundaries around a group of entities with one being an undisputed root and others being its linked children. As Martin Fowler writes:

Aggregate is a pattern in Domain-Driven Design. A DDD aggregate is a cluster of domain objects that can be treated as a single unit. An example may be an order and its line-items, these will be separate objects, but it’s useful to treat the order (together with its line items) as a single aggregate.

An aggregate will have one of its component objects be the aggregate root. Any references from outside the aggregate should only go to the aggregate root. The root can thus ensure the integrity of the aggregate as a whole.

An Apollo mission seems to be clearly an aggregate (of Client and Mission Plan objects, traversals, operator assignments, etc.) and as such a prime candidate for an Apollo microservice.

Decision

There shall be a Missions service tasked with coordinating the planning and execution of missions and so coarsely (this is not software design):

4c176d974c4ac0a749c3bc1307d7066c-Apollo.jpg3d987be4e84093cc93ac34da8d0e1416-diagrams-missionservice.drawio.png

Mission Intake

Apollo exposes Mission Request API, through which it receives requests from its clients (e.g.: HubOps) to accept Mission Requests for execution. Mission Requests are sets of objectives and each is an action the UAV is expected to perform when it reaches the destination set for the objective. Objectives is a closed but growing set of things that the UAV should be capable of performing and they could be:

  • hover for 1min;

  • deliver a named payload with the winch or to a mailbox

    • Naming the payloads helps turning a Mission Request into a set (as opposed to list) of co-dependent objectives; you need to be able to pick the parcel A before you can think of delivering it.
  • pick a named payload;

  • perform a orthomosaic survey or a region

    • The location is a region so the Missions Service needs to pick the survey entrance and calculate path to it and then it needs to calculate the path from the survey’s exit to the next objective

A Mission Request could look like so.

Apollo takes the mission through its entire lifecycle keeping the client updated with a stream of notifications that helps the client keep their clients informed and calculate ETA.

Additionally the Mission Request API should allow:

  • Cancelling a once submitted Mission Request. This will have a deadline similar to canceling an order on amazon.com and that deadline will have to be operationally determined;
  • Submitting a “Dry-Run” Mission Request. This is when we will respond to the clients with compiled mission, but won’t proceed any further and won’t persist this mission anywhere.

Mission Compilation

The Missions Service sequences the objective in what it things is the right chronology of executing them and, using the Pathfinding service plots paths/traversals between consecutive objectives. It may do it a couple of times, at least:

  • To present to the operator and let them review it, perhaps even influence it;
  • As the final committed plan just before the takeoff to make the plan is most up-to-date against the ever changing environment;
  • One could even imagine needing to recompile and ongoing mission - maybe because it meets an obstacle that it can go around staying within its pre-committed contingency flight volume. This looks in scope for the Missions Service - further into the future. It requires a means for the UAV to escalate the need to replan towards the Missions Service (and via Avatar).

Missions Service submits this plan (the contingency flight volume) to a UTM system so that the authority (FAA in the US) and other traffic participants can see it and plan around it. The UTM performs all the necessary validation to make sure the flight complies with the regulations and doesn’t collide with other flight plans.

Operator Assignment

Apollo exposes Mission Claimer API, an internal, operational interface, through which it allows Apollo Operators subscribe to and list new missions seeking acceptance. The Operators review and accept compiled missions much like an Uber driver accepts a new ride.

When an Operator accepts a mission, the Missions Service arranges for it to connect to the UAV that would execute it, that is it:

  • Solicits the Avatar to represent the UAV.
  • Hands over the Mission Plan to the Avatar.
  • Asks the Avatar to accept control from specific Operator(s).
  • Hands over the Avatar’s host address to the Operator(s) so that they can connect;

The Missions Service indeed deals with external entities such as Operators, UAVs and their Avatars, but they are opaque identifiers to it.

Mission Event Log

The Missions Service subscribes to the mission_id PubSub topic, to which the Avatar writes/logs every event that it sees (including every telemetry frame) - this is the Mission Event Log. The Missions Service does so to continue being an observer of the executed mission, so that:

  • it is able to keep its client up-to-date with the mission’s progress and help it calculate the ETA. The Missions Service doesn’t forward every event to the client, just those that are relevant for the client to calculate the ETA from (so perhaps every other telemetry frame).
  • it is able to detect that the mission has completed (or aborted, emergency landed) and use it to update the mission status in its Postgres storage.

The Mission event-log (telemetry and pilot actions) should accumulate on the persisted PubSub for the duration of the mission and perhaps for a couple of days beyond it. This gives the Missions Service a time-windowed access to this log and allows it to add it to the mission description - should anyone ask. It a big dataset (couple of MB) of no transactional value nor specific indexing requirements (i.e.: we don’t need to search missions by the airspeed or pilots requesting the drone to pause). It thus should not be be written to the Missions Service Postgres storage, but rather to a file in a bucket for analysis. It’s probably not the Missions Service who should do this archiving.

Consequences

Can’t think of any.

Alternatives Considered

The proposed Missions Service has quite a bit to do, so it is too big and should it be split? It does have a quite a bit to do, but everything it does is tightly close to servicing/coordinating the mission. Splitting it between planning and execution doesn’t seem very productive as all the Missions Service does during execution is event log forwarding. The list of things the Missions Service does its long, but shallow. We think it does present high-cohesion. We think it does represent the mission aggregate well at the lower bound:

Lower bound: A Microservice should consist of no less than an Aggregate (or at least an independent Entity) and the associated Services that operate on the entities of that Aggregate.

Appendix A - Prospective Mission Request

{
    "uav_id": "hs82y1nsk",
    "initial_payload": "A",
    "objectives":
    [
        {
            "type": "deliver",
            "parameters":
            {
                "method": "winch",
                "location":
                {
                    "lng": 51.02202,
                    "lat": 17.31732
                },
                "payload": "A"
            }
        },
        {
            "type": "pickup",
            "parameters":
            {
                "method": "mailbox",
                "location":
                {
                    "lng": 51.028,
                    "lat": 17.32732
                },
                "payload": "B"
            }
        },
        {
            "type": "pickup",
            "parameters":
            {
                "method": "mailbox",
                "location":
                {
                    "lng": 51.038,
                    "lat": 17.3282
                },
                "payload": "C"
            }
        },
        {
            "type": "deliver",
            "parameters":
            {
                "method": "mailbox",
                "location":
                {
                    "lng": 51.138,
                    "lat": 17.3182
                },
                "payload": "C"
            }
        },
        {
            "type": "deliver",
            "parameters":
            {
                "method": "land",
                "location":
                {
                    "lng": 51.1091,
                    "lat": 17.45221
                },
                "payload": "B"
            }
        },
        {
            "type": "survey",
            "parameters":
            {
                "method": "ortho",
                "gimbalVAngleDeg": 45,
                "region":
                [
                    {
                        "lng": 51.02802,
                        "lat": 17.38732
                    },
                    {
                        "lng": 51.03802,
                        "lat": 17.37732
                    },
                    {
                        "lng": 51.04802,
                        "lat": 17.35732
                    },
                    {
                        "lng": 51.02802,
                        "lat": 17.38732
                    }
                ]
            }
        },
        {
            "type": "survey",
            "parameters":
            {
                "method": "orbit",
                "imageNo": 12,
                "center":
                {
                    "lng": 51.02802,
                    "lat": 17.38732,
                    "agl": 0
                }
            }
        }
    ]
}

The above is just an example expression that solely serves to show the complexity and the kinds of things a Mission Request would have to carry. Proper study and requirements gathering needs to take place to make sure this expression captures the clients’ needs. An important questions that needs asking is:

  • Will the client ever need to parameterise the traversals? For instance, suppose the UAV needs to pick a dangerous object up from A and deliver it to B.
  • Will the client ever need to impose the chronology of the objectives? Or is insisting on sets an over-complication?
    Answering those is not in scope of this ADR and neither is specifying the Mission Request format/IDL.
Last updated on