At Upvest we always start with the question: “Why?”. Every engineer first needs to understand the problem we are trying to solve, and why it is important to our clients and us. Only once they’ve done this can they move on to thinking about solutions for the problem. Every engineer needs to have a product mindset. That’s why we call these people Product Engineers.
Every day, we work closely with our counterparts in the product team to deliver value to our clients. The product team figure out what to build and why. The engineering team figure out how to build it, who builds it and how long it might take.
We operate in a “you build it, you run it” model at Upvest Engineering. When we set out to design the platform, we took some notes from Domain Driven Design and formed our teams around core business domains/contexts.
Each team has full ownership of their domain from start to finish, from conception and validation of ideas, via definition, design, implementation, testing, deployment and monitoring to operational support and maintenance. Along the way, they take input and feedback from relevant stakeholders in the company and align with our business and product strategy, but it is the domain team that ultimately makes all the decisions. We prefer not to write strict policies but rather build a set of core engineering principles teams can rely on to support their decision-making. In this way, teams are engaged in the whole life-cycle of the functionality they develop. We’ve found this is empowering for engineers.
A Bit About Technology
We are a cloud-native company and have been building on the Google Cloud Platform from the start. To allow our domain teams to move independently we’ve adopted a loosely coupled, event-driven, micro-services architecture.
We build all our services with Go, a great tool for this kind of work. GitHub Actions drive our Continuous Integration (CI) and ArgoCD deploys all our containerised services to Google Kubernetes Engine.
We use LinkerD as our service mesh. This abstracts some common functionality which used to live at the application level (retries, circuit breaking, etc.) and provides connectivity and encryption. We rely on a robust open-source database: PostgreSQL, managed by Google CloudSQL. Underpinning all of this is a battle-proven event streaming platform Kafka managed by Confluent Cloud.
If there is a need for synchronous connections between services, we use gRPC which gives us both high-performance and static typing. We expose our JSON/HTTPS API (not strictly speaking a true REST API) through Kong API Gateway which provides access control, rate limiting and handles authentication of incoming API calls. We use the Phantom Token approach so internal JWTs are never exposed outside our system but instead get exchanged for an opaque access token at the gateway layer.
Our API is quite asynchronous and many endpoints will return immediately but start a process in the background. In these situations, we communicate back to the client backend using webhooks.
We use Datadog for observing what is happening in the system at all times. We provide self-serve data access to the business using Google Big Query and Looker.
We adopt standards where we can, so our API is defined in an OpenAPI specification. We’re trying to use AsyncAPI internally, to document our event interfaces between domains, though this is still very much a work in progress. All our internal APIs (AsyncAPI, gRPC/Protobuf, etc) are catalogued in our internal API catalogue, Apicurio.
When things go wrong we use incident.io to facilitate our incident management process.
Our API documentation is currently being rebuilt on top of Doctave. This documentation is not publicly available today but we’re aiming to publish it soon. Stay tuned.
Our Platform Tribe takes this set of tools that we have converged on and delivers it as a product with meaningful, additional tooling and automation for all of the Product Engineering teams. This allows those Product Engineering teams to focus on building the business functionality, with the assurance that they can be productive on the developer platform provided. Both our Platform team and Security team work hard on “shifting left” instead of throwing things over the fence for someone else to pick up so that everyone can have more ownership of the pieces they rely on.
Developer and Engineering eXperience
When we started designing our API, the number one problem we set for our engineers to solve was “how can we make it as easy and smooth as possible for the developer on the other side integrating with our API”. This is such a focal point for us that we have created a whole function around Developer eXperience (DX) to make sure all our clients have a smooth and successful journey with our platform. The ultimate goal is to have an API with supporting materials that clients can integrate without any support from us. A true self-service experience.
Whilst Upvest’s DX tribe is concerned with external developers interfacing with our product, we also have an equally important Engineering Experience function (which I briefly touched on already above) which focuses on making sure our internal Engineers have suitable tooling and automation in place and can work effectively, productively and happily.
This is only the beginning
This is a marathon, not a sprint. Two years ago, we were 6 engineers with zero lines of code. Now we are close to 60 engineers spread across Europe and Africa and we’ve built a brand new investment core-banking system from scratch. We are in production, live with our first clients. But there is so much more still to do…
If you think the kinds of challenges we are dealing with are interesting, check out all our open roles at our Careers Site.