Add solution.md

This commit is contained in:
Tim Kächele 2025-04-22 15:36:56 +02:00
parent a718c60561
commit 843121594c

93
solution.md Normal file
View File

@ -0,0 +1,93 @@
# Technical interview task
## Running the program
You can run the api server using the following command
```
$ DB_CONNECTION=/path/to/database.sqlite3 uvicorn app.api:app
```
## Process
1. We validate the request, if the request does not have all attributes or invalid data (e.g. no limit price in a limit order) the request is rejected and the user gets an error
2. We store a new order in our persistence layer (sqlite3 in our example)
3. We submit the order to the exchange
4. We update the status of the order
## Flow of an order
```plantuml
Developer -> API: Create order
activate API
API -> API : Validate order request
alt Invalid request
Developer <- API: Send failure response
end
API -> Database: Store order record
API -> Exchange: Place order
API <- Exchange: Receive response
alt Success
API -> Database: Mark order as placed
Developer <- API: Send success response
else Failure
API -> Database: Mark as failed
Developer <- API: Send failure response
end
deactivate API
```
## Considerations
### Persistence layer
I picked sqlite3 as a stand in for a relational database server that is easy to use for testing/development.
In a real world scenario a client/server relational database could be considered, e.g. postgresql.
A relational database is a rather versatile pick, allowing a plethora of querying patterns. This versatility comes at the cost
of performance. Not knowing the exact persistence layer requirements a relational database is a decent choice that should handle most load patterns in the beginning. Knowing more about the querying patterns and having a demand for more throughput, one could could consider e.g. storing relevant orders in memory and implementing a write ahead logging based system.
### Exchange latency/througput
Exchange operations showcase a very high latency. That shouldn't lead anyone to think that the exchange can't handle a large number of requests, but it means that we may need to handle a large number of in-flight http requests. Ideally we are able to perform the `place_order` request aysnchronously, e.g. via async/await.
#### Backpressure considerations
Using async/await interfaces sounds like a good idea, but in reality it can lead to significant problems. Especially because async/await apis often hide the concept of back pressure. This prevents the developers of the program to fine tune the throughput of the system and setting upper boundaries (e.g. by using fixed sized queues) which would prevent system overload.
We could consider using a more explicit concurrency approach such as a basic actor model or having a thread pool executing the place order requests.
### Guaranteed executions and retries
This is in response to the third (been saved in the database **AND** it is guaranteed that the order **will** be placed on the stock exchange.)
and fifth requirement. (exchange should not impact the reliability of the `POST /orders` endpoint)
#### What does it mean to guarantee a placement?
We can only guarantee that an order was placed once we got the result of the exchange.
Placing an order is not just the expression of an intent to buy/sell a certain instrument at a given price. There's also a timing component to consider.
You may be willing to buy an instrument **now** for a given price, but not in five minutes when the prices have changed.
Therefore guaranteeing an order placement is trickier than expected. It is a time sensitive operation. While a retail client that wants to buy a stock
as a long term investment may not care all that much about the exact timing of an order placement, institutional clients such as liquidity providers using the brokerage do care, they may only place an order in the hopes of it getting filled in the next e.g. 300ms and intend to cancel it afterwards.
For such a client guaranteeing the placement may not be in their best interest as they may prefer not to interact with the market at all instead of the uncertainty of when the submitted order is placed. A stale order can cost you more money than not quoting at all.
Considering this context we are going to fail fast and transparently and not e.g. store the order in the database and retrying to place the order a couple of times using a background job.
#### Reliability
Unless we have some kind of system to internalize order flow we are dependent on our partner exchanges to be available, otherwise
we can not run our brokerage, therefore if the exchange is not available we can only respond with a predefined error code.
## Improvements
- **Idempotency:** Networks are unrealiable, it's therefore best to have some kind of idempotency key provided by the api client that can ensure that an order is only ever placed once even when multiple attempts are made.
- **Advanced order controls** As previously mentioned, different clients have different timing requirements and some may be okay with a higher uncertainty when an order is placed due to stock exchange downtimes. By allowing clients to express their risk appetite and to prioritize placement certainty over latency by allowing placement retries we can cover both the retail as well as the institutional client's requirements.