Mercari Hallo’s Tech Stack and Why We Chose It

Hello! I’m @napoli, a software engineer (Engineering Head) for Mercari Hallo. This is the third article in the Mercari Hallo, World! series, which is a behind-the-scenes look at Mercari Hallo’s development.

In early March 2024, we launched a new service called Mercari Hallo. Mercari Hallo is an on-demand work service enabling users to work in their free time for as little as one hour.

In this article, I’ll explain the tech stack and architecture we used when creating Mercari Hallo, as well as the reasons for our decisions.

What you’ll learn in this article

  1. The big picture of Mercari Hallo’s tech stack and architecture
  2. How and why we chose this tech stack
  3. Tips for how to choose a tech stack when starting a new service

Main tech stack

The main tech stack used for Mercari Hallo is as follows:

  • Backend

    • Go
    • Google Cloud Platform (GKE, Cloud SQL for PostgreSQL, etc.)
    • GraphQL
    • gqlgen
    • ent.
  • Frontend

    • React / TypeScript
    • Next.js
    • Apollo Client (React)
  • Mobile app (the standalone Mercari Hallo app)

    • Flutter / Dart

We use modular monolithic architecture for the backend and the monorepo repository management method.

Modular monolithic architecture

Around April 2023, Mercari Group decided to enter the on-demand labor business and formed a new team to do so. Initially, the plan was just to build a proof of concept (PoC) to see if Mercari could bring unique value to this domain, and then grow the service if it seemed promising. This meant that the team was expected to rapidly build the service with only a small number of people. (In the early days, the team only had one or two engineers!)

Given the situation, we decided to take the modular monolithic approach for the backend (server). The Mercari marketplace app, Mercari Group’s main service, evolved from a monolithic architecture to a microservice architecture as it grew. Modular monolithic architecture is somewhere between these two approaches—to put it simply, it integrates microservices into a monolithic system. Looking back on it now, I think this was the right choice.

Easy connections between services

In modular monolithic architecture, one server contains all functions expected of an API server. All functions run in the same program, but the functions are actually independent modules within the server. The modules connect to provide functionality as an API. (We call these modules “services” in Mercari Hallo.)

When I say “one” server, I mean one server program (or one deployment unit). Because all services are implemented in one server, there’s no need for remote procedure calls (RPCs). Unlike microservice architecture, which may use multiple RPCs to provide one API, functionality is completed by calling functions within the same program. This means we don’t need to worry about defining protocols for communication between services or handling network errors, which makes the implementation work and design significantly easier.

Transactions with a single database

In addition to the backend being a modular monolith, Mercari Hallo uses a single instance for its main database. This structure enables the database’s transaction functionality to be utilized to its full capabilities. Mercari Hallo has many cases where the integrity of data is extremely important, such as information regarding wages. Database transaction functionality is extremely powerful in this regard; data inconsistency between services, which was a major point of concern in microservice architecture, is not much of a problem. This also made implementation work and design much easier.

Small amount of infrastructure-related code

Mercari Hallo uses the IaaS service Terraform. Modular monoliths generally run on a single server, so the amount of infrastructure-related code needed is smaller than when using microservices. Engineers who specialize in application domains, such as APIs, often find that configuring and testing the infrastructure takes longer than they expect. Not needing much code for infrastructure and instead enabling engineers to focus on implementing the API was a great help for Mercari Hallo’s quick development.

Points to keep in mind

While modular monolithic architecture was a good choice for Mercari Hallo, there are some things to keep in mind.

One large concern is that the initial design tends to be difficult. If you aren’t careful with how you design the system, it can easily turn into just a regular monolith. Monoliths aren’t inherently bad, of course, but not separating the modules or services within the system appropriately according to their scope of responsibility can lead to large problems. If the system isn’t appropriately separated into modules, it can be difficult to reuse functionality, and modifying one thing somewhere can have unintended effects elsewhere. A system with complex interdependencies is both hard to understand and hard to test, increasing the likelihood of system failures. As a result, rapid functionality development becomes more and more difficult as time goes on.

One advantage of microservice architecture is that you’re basically forced to separate modules/services on the infrastructure level. In addition to the size of programs being different, databases are generally independent for each microservice, so changes to one service don’t have a direct impact on other services. This does depend on the granularity you choose to use for microservices, but developers are essentially required to think about the appropriate size and scope of modules/services. And because each service is independent, the scope of responsibility tends to be clear. Microservice architecture also works well for large organizations; it’s easy to assign ownership of each microservice to individual teams.

For better or for worse, the modular monolith doesn’t force you to separate modules/services—but it’s just as important to get this right as it is in microservice architecture. Whoever is in charge of the initial architecture design needs to design the modular monolith very carefully, and the developers all need to understand it well as they implement functionality. This is a fairly difficult task.

That said, I think the modular monolith approach is good for quickly developing a new product, like Mercari Hallo. If you know from the beginning that it will become a large-scale product, a distributed system like microservice architecture is also an effective choice, but in most cases, it’s okay to wait to physically separate the system until after the scale of the product has grown from a business perspective.

As a side note, this isn’t the first time a Mercari product has used modular monolithic architecture. In that instance, unlike Mercari Hallo, the developers migrated the system from a monolith to a modular monolith. You can read about it here:
Making a Modular Monolith out of Mercari’s Transaction Domain (Available only in Japanese)

Monorepo

Mercari Hallo uses a monorepo. This means that we manage all components that make up the system, such as the backend and frontend, in a single repository, while maintaining independence for each component.

In this section, I’ll list some of the reasons that I think this approach was the best choice for us.

Ability to see the whole system in one place

Using a monorepo means that all of the necessary system components are stored in one repository. This makes it very easy to see the whole system at a glance. When Mercari Hallo was first getting started, we didn’t have many engineers, so there were times when a single engineer would be working on the backend, frontend, and mobile app all at once. Having all the code in one place is a big advantage in terms of ease of development. If you’re working on the backend or mobile app and want to see how something is implemented in the frontend, you can simply search the files from your IDE or editor to find the answer right away. You don’t need to go through the hassle of switching to a different repository, running git pull, switching to a different window, and so on. Of course, code written for other areas in different programming languages may still be hard to understand, but it’s at least easy to search for the code across the entire system.

Ease of code reviews

All pull requests are on the same GitHub repository, so it’s easy to review them even across domains. For implementations that involve specialized knowledge, it’s better to have someone with that knowledge review the code, but in many cases, members in other domains can review simple modifications to the code well enough. Mercari Hallo requires pull requests to be approved before they can be merged into the main branch, so the speed of reviews is crucial. The less time it takes to merge a pull request into the main branch, the less likely it is that a merge conflict will occur. This means that the time spent on resolving conflicts goes down, QA is easier, and we can focus on more important tasks. This is also doable with a multi-repo setup, of course, but a monorepo is definitely easier.

Sharing GraphQL schema files

Mercari Hallo uses GraphQL (which I’ll go into detail about later on) for communication between the backend and the frontend/mobile app. By sharing the GraphQL schemas that are created on the backend within the same repository, we were able to automatically generate the GraphQL client code for the frontend and connect the two easily. Even beyond GraphQL schema files, not needing to go out and fetch necessary files remotely is just convenient overall, and helps stabilize the development environment.

Sense of unity from working in the same place

This isn’t really technical, but bear with me for a minute: Using a monorepo makes it feel like we’re all working together to develop the product, even though we’re in different domains, like frontend and backend. Similar to what I said above about being able to see the whole system in one place, you can see how frequently and how devotedly people in other domains carry out their work. This is surprisingly important for development projects that require close communication. It’s not something you can quantify, but I think it had a good impact on Mercari Hallo’s development team.

The structure of our monorepo

The main languages we use for Mercari Hallo are Go, Dart, and TypeScript. We have directories for each language directly under the root of the repository. This makes it easier to manage the ecosystem and CI/CD. In day-to-day development work, this also has the benefit of enabling engineers to work in basically independent environments even within the same repository. For example, someone working on the backend can generally stay within the Go directory.

Independent build environments

In Mercari Hallo’s monorepo, each component (backend, frontend, etc.) has their own independent build technique. There are tools like Bazel out there for centralized build management, but we don’t use them. When Mercari Hallo was just getting started, we were fortunate to have highly skilled developers in each domain, so we used the standard build techniques they were most familiar with. This enabled us to seamlessly set up build environments, since the developers didn’t have to learn new technology. We haven’t run into any particular problems with this on the operations side, either. Not to say that there aren’t any benefits to using centralized build management techniques across components, but they also come with their own difficulties. Unless you have a clear reason for wanting to use centralized techniques, I’d recommend setting up independent build environments for each component.

So, those are some examples of the benefits of a monorepo and how we use it for Mercari Hallo. It’s often said that the monorepo approach has the disadvantage of the repository getting too big, but given the network and local environments commonly used these days, I don’t think you have to worry about that unless the service gets really large-scale. There are some other minor drawbacks, but I think the benefits outweigh them significantly. I recommend the monorepo approach when starting up a new service.

Infrastructure at a glance

Mercari Hallo uses Google Cloud Platform for infrastructure as much as possible. This is basically what it looks like:

The backend runs on a GraphQL server using Google Kubernetes Engine (GKE). Gateway, which precedes the API, and Next.js also run on GKE. We use Cloud SQL for PostgreSQL for the database, Redis for memory storage, Fastly for the CDN, and Cloudflare as an image optimization (conversion) service.

Google Kubernetes Engine (GKE)

Mercari Hallo uses Google Kubernetes Engine (GKE) for backend infrastructure. We chose GKE for two main reasons:

Use and expertise within Mercari Group

Many Mercari Group services run on GKE. We have a Platform Team in charge of operation and maintenance. GKE (Kubernetes) is generally difficult to understand for engineers who aren’t specialized in infrastructure, but Mercari Group has plenty of tools, documentation, and best practices for efficient configuration and development. The Platform Team also provides thorough support. Thanks to this, we ran into relatively few problems.

Integration with the Mercari ecosystem

Many of Mercari Group’s services use GKE and communicate between services on the same cluster using gRPC. This enables us to securely and efficiently use existing services. Mercari Hallo needed to use a few of Mercari’s existing microservices, so being able to use these services easily and securely was a big advantage.

Other options

We chose GKE for Mercari Hallo because there was support for it within Mercari Group and because it was important to be able to integrate with the existing Mercari ecosystem. That said, the construction and operation is somewhat difficult, so if you’re creating a standalone service from scratch and you don’t have anyone with the right expertise, I think an easy-to-use serverless environment like Cloud Run is a viable option as well.

Backend / Go

We use Go for backend development. Go is the standard backend language in Mercari Group and stands far above other languages in terms of expertise and resource allocation within the company. It’s also well suited to API development; execution speed is fast, and goroutines enable powerful parallel processing.

Go is also a simple and easy-to-read language—so simple that two people coding the same thing will generally write the same code. This is a huge plus when you have a large amount of people working on the code; it significantly lowers the difficulty of understanding what’s going on and doing code reviews. I also find it easier to notice problems in the code in Go over other languages.

When code is complex, it can be difficult to decipher what the code is doing and why, even if you’re the one who wrote it in the first place. In that sense, Go may very well be a language that’s even easier for the reader than it is for the writer. Being easy for the reader is a large advantage when operating a service in the long term, because as the years pass, the time spent reading the code becomes longer than the time spent writing the code.

I also personally really like Go, so I think at the moment it would be my top choice for API development, even outside of Mercari.

Cloud SQL for PostgreSQL / Ent / Atlas

We use Cloud SQL for PostgreSQL for our database. Many Mercari Group services use Google Cloud Spanner, but we chose to use Cloud SQL for PostgreSQL for the following reasons:

Low learning cost

PostgreSQL is an RDBMS, which many engineers have knowledge of and experience in, so there isn’t much they have to learn to get started. A low learning curve means that it’s easy for new members to jump into development and hit the ground running.

Rich ecosystem

PostgreSQL has been around for a long time, and there are plenty of third-party tools and libraries built for it. Having tools and libraries at your disposal leads to efficient development, and is no small advantage. There are also high-functioning GUI tools, which is very useful for directly adjusting data while debugging.

Portability

PostgreSQL is provided as a Docker image, and can be easily run locally on Docker. This makes it easy to run unit tests involving the database, and also to set up a local environment similar to the remote development server.

The nature of Mercari Hallo’s service

Given the nature of the Mercari Hallo service, we read the database much more than we write to it. As a result, we decided that a single instance would be able to handle all write commands, at least for the foreseeable future. For reading the database, we believe that creating read replicas as necessary will be enough to handle most traffic we get.

In addition to these points, the low startup cost is generally considered to be another advantage of PostgreSQL. For Mercari Hallo, we didn’t particularly take this into consideration since we were expecting a large number of users from the start, but I imagine the startup cost is important for many new services.

ORM

We use Ent for object-relational mapping (ORM). Ent is a powerful ORM framework for Go. It’s been used in a number of other places within Mercari Group, so we decided to use it for Mercari Hallo as well. It uses a code-first approach and has advanced query generation features, so it enables us to efficiently manipulate the database through Go.

In many cases, using ORM makes it difficult to write optimized and flexible queries, but at Mercari Hallo, our API is generally made up of very simple queries. This does mean that sometimes the number of queries gets large, but having it all be easy to understand and easy to implement is a huge plus. Now, you may be wondering what the performance is like if we have a large number of queries, but read processing scales just fine if we increase the number of read replicas, and unless the API is accessed at a seriously high frequency, having a slightly high number of queries won’t cause problems as long as you use indexes appropriately. Simple queries also make indexing easier. In reality, we haven’t had any major database performance problems for Mercari Hallo.

Database migration

We use Atlas for database migration. Ent also has an auto-migration feature and automatically applies DDL for the differences, but Ent’s auto-migration by itself often doesn’t meet the requirements once it’s time to actually start operating the service, so we generally stick to Atlas for management. (We have Ent’s auto-migration turned off in the production environment.) Atlas connects with Ent and automatically generates schema differences, among other powerful features, enabling efficient migration work. We also use Atlas for some migration work in DML.

GraphQL

We use GraphQL for communication between the backend and the frontend, including the mobile app. GraphQL is a modern choice for API development, and is used in many services around the world. It enables the service to dynamically control the data that’s fetched on the frontend, so even if the frontend specs change, you don’t necessarily have to make changes on the backend side. You can also nest queries, so the frontend can fetch most of the data needed for a screen with one API call, reducing unnecessary API calls. Also, the interfaces are defined by schemas with a static type system, enabling precise data exchange between the backend and the frontend. It’s helpful that the IDE/editor’s completion features tend to be effective as well.

gqlgen

On the backend, we use the Go GraphQL server implementation gqlgen. It’s simple, yet it has all the features we need; it has a low learning cost and is easy to use.

gqlgen is a schema-first framework and generally involves defining queries/mutations in one schema file, so Mercari Hallo has all of its queries and mutations collected in one schema file. This does mean that the file has gotten very long and sometimes feels a bit hard to use, but we take steps to make it as easy to maintain as possible, such as by using graphql-eslint to automatically clean up the file and sort types/queries/mutations alphabetically.

There are many benefits to having schemas all in one file. It’s easy to see everything in one place and automatically generate code, and when we want to show Mercari Hallo’s API to other teams, we can just show them the one file.

One other plus is that it has a simple and easy-to-use playground. With a playground, you can actually test the GraphQL queries/mutations you write. It will automatically complete input for you and generate API references (documents) for queries/mutations. This makes life so much easier and is very helpful when debugging and testing. With gqlgen, it’s easy to set up a playground without having to go through any complex configuration.

That said—and this isn’t about gqlgen specifically, but—with GraphQL server implementations, you have to be careful of the N+1 problem. Because of this, the learning cost and implementation cost is a little higher compared to REST, for example, but I don’t think this is too significant of a drawback when using GraphQL. This problem is usually addressed by using dataloaders, and Mercari Hallo uses graph-gophers/dataloader.

It’s also worth noting that protocol buffers (gRPC) are widely used as a protocol within Mercari Group, and they have excellent functionality. But in my opinion, when building a general web service, GraphQL is overall easier to use as a communication protocol between the frontend and the backend. (Though of course, this depends on the kind of service you’re building.)

REST tends to be brought up as an alternative, but in this day and age, I don’t think there are any benefits to choosing it unless you have a clear reason to.

React / TypeScript / Next.js

Mercari Hallo also uses a web-based frontend. The Work tab within the Mercari app uses WebView, and the management screen for partner businesses, intended for desktop use, is also web-based.


The Work tab within the Mercari app and the partner business management screen

We decided on React right away. Vue was also suggested as an option because it’s used in other projects within Mercari Group, but everyone on the initial development team was familiar with React, and it had all the features we needed for the service, so we figured that it would make development more efficient. We also thought that, given industry trends and the talent pool, React would provide us with an advantage on the hiring front.

The decision to use TypeScript was also a no-brainer. These days, static typing is basically a requirement for frontend development, and it offers many benefits for efficient development. It may be a bit more difficult than JavaScript, but there’s so much information out there to reference, so this shouldn’t be much of a problem for teams with a certain level of knowledge and experience.

We decided to use Next.js because of past experience using it within Mercari Group, how easy it is to use, the fact that it’s based on React, and its high performance.

The Work tab is right on the Mercari app, which demands a high-quality user experience. This means that display speed is crucial. We haven’t yet made full use of Next.js’s capabilities, but it offers flexible configuration for performance improvement, so we plan to leverage this as necessary going forward.

We use Apollo Client as our GraphQL client. Apollo Client is a popular web frontend framework and has excellent functionality, enabling efficient development. We chose it because it’s been used with Mercari Group in the past. We also use React hooks to integrate with React.

Flutter / Dart

In addition to the tab within the Mercari app, Mercari Hallo also has a standalone mobile app for iOS and Android. (You can find it by searching for メルカリ ハロ on the App Store or the Google Play Store!)


The standalone Mercari Hallo app

We use Flutter and Dart as the base for this app. We also considered the following options at the start of development:

  1. iOS/Android native (Swift/Kotlin)
  2. React Native
  3. WebView-based app

The decision of what technology to use for the mobile app took more time than for the web frontend. Each of the choices had around the same amount of pros and cons, and the first two in particular were the subject of widely varying opinions from different team members. The discussions had to involve stakeholders from across the group, not just the Mercari Hallo team, so it was difficult to come to a final decision. The discussions mainly revolved around the following points:

  • Development cost
  • Proficiency level of the team
  • Performance
  • Internal resource allocation
  • Richness of the ecosystem, including third-party libraries
  • Ease of use
  • Expertise within Mercari Group

Of these, development cost drew the most attention. We didn’t have many engineers on the team at the beginning, but given the state of the market, we needed to release quickly. Developing native apps for both iOS and Android would have nearly twice the development cost, and there’s no guarantee we would be able to release on both platforms at the same time. The team really wanted to release the standalone mobile app on both iOS and Android at the same time, so we felt that developing native apps would be too risky timeline-wise.

On the other hand, the Mercari app (as opposed to the Mercari Hallo app) is implemented as a native app for iOS and Android. This meant that internally, iOS/Android native development had a much higher standing in terms of resource allocation. Of course, outside of Mercari, there are many people who can develop in Swift and Kotlin, too. But as I’ve already mentioned, we didn’t have many engineers on the team at first, and due to various circumstances we had no guarantee we would be able to get engineers even from other teams within the company. (Similarly, the US version of Mercari is implemented using React Native, so React Native also had more support in terms of expertise.)

Some stakeholders also voiced concerns regarding performance. There’s no arguing that iOS and Android native apps are the best option in terms of performance. There were concerns that even if we used a cross-platform framework like Flutter now, we would eventually have to rebuild the apps natively, but we decided that for now, launching the service in a reasonable timeframe and getting it out there to users was more important than optimal performance. Thankfully, given the nature of the service, there aren’t many cases in which we would need to maximize performance on iOS/Android anyway. It’s also worth pointing out that the Mercari app was rebuilt from scratch around four years after it launched. With this experience under our belts, we decided that it was more important to get the service on track now and if necessary, switch to iOS/Android native apps a few years down the line.

Eventually, after analyzing all of these discussion points, we decided that Flutter seemed like the best match for Mercari Hallo.

We don’t know for sure that this will turn out to have been the right choice for the Mercari Hallo service, or from the perspective of Mercari Group as a whole. But at least right now, it feels well-balanced in terms of development cost and performance, so I think it was an appropriate decision.

Conclusion

So, that was a quick introduction to the tech stack and architecture we use for Mercari Hallo, and the process we went through to choose it.

Selecting the right technology when launching a new service is really difficult; the decisions need to take into account many different perspectives in order to make sure the business succeeds. It’s also a serious responsibility, since in many cases it’s near impossible to change the technology after you’ve started. At the same time, I think many engineers find this process fun and worthwhile.

There’s no one “right” answer, since the basis for these decisions depends on the scale and conditions of the company, but I hope this example of how we did it for Mercari Hallo is a helpful reference for anyone looking to start a new service!

Links

Series feature: Mercari Hallo, World!

Mercari is hiring! If you’re interested in Mercari Hallo development or in Mercari itself, we’d love to hear from you. See the links below for details.

  • X
  • Facebook
  • linkedin
  • このエントリーをはてなブックマークに追加