androidengineers.Book a session

Scalable Architecture

Integrating with Microservices & Graph Layers

article25 minHard

As backend architectures evolve toward microservices, mobile clients face a new challenge: instead of one REST API, they're talking to dozens of services. The Backend-for-Frontend (BFF) pattern and GraphQL are the two dominant solutions.

The Problem with Direct Microservice Calls

Mobile App → User Service (auth)
           → Article Service (content)
           → Comment Service (engagement)
           → Media Service (images/video)
           → Notification Service (badges)

Problems:

  • Multiple network round-trips per screen
  • Each service has its own API contract, versioning, auth
  • Overfetching: Article Service returns full article even for list view
  • Hard to optimize for slow mobile networks

Solution 1: Backend-for-Frontend (BFF)

The BFF is a dedicated backend layer, owned by the mobile team, that aggregates multiple microservices into a single, client-optimized API:

Mobile App
    └── BFF (owned by mobile team)
            ├── User Service
            ├── Article Service
            ├── Comment Service
            └── Media Service
// Client sees ONE API instead of five:
GET /api/mobile/feed?userId=123
→ Returns: articles with authors embedded, thumbnail URLs sized for mobile, unread counts

Advantages:

  • Client-optimized payloads (no overfetching)
  • One auth token, one endpoint
  • Mobile team controls API evolution
  • Backend team doesn't need to know about mobile screen layouts

Disadvantages:

  • Additional service to maintain
  • Can become a bottleneck/single point of failure
  • Requires coordination between mobile and backend teams

Solution 2: GraphQL

GraphQL lets the client declare exactly what data it needs in a single query, even if that data spans multiple microservices:

query FeedQuery($userId: ID!) {
  feed(userId: $userId) {
    articles {
      id
      title
      thumbnail { url width height }  # only what mobile needs
      author { name avatarUrl }
      commentCount                     # from Comment Service
    }
    unreadCount                        # from Notification Service
  }
}

On Android with Apollo:

val apolloClient = ApolloClient.Builder()
    .serverUrl("https://api.example.com/graphql")
    .okHttpClient(okHttpClient)
    .build()

viewModelScope.launch {
    val response = apolloClient.query(FeedQuery(userId = currentUserId)).execute()
    val articles = response.data?.feed?.articles ?: return@launch
    _uiState.value = UiState.Success(articles.map { it.toUiModel() })
}

Advantages:

  • Client drives query shape — no overfetching or underfetching
  • Single endpoint, single auth
  • Strong typing from schema

Disadvantages:

  • Requires GraphQL server (or gateway)
  • N+1 query problem needs server-side DataLoader
  • Caching is more complex than REST

Structuring the Data Layer for Microservices

// Repository abstracts whether data comes from one or multiple services
class FeedRepository(
    private val articleService: ArticleApi,
    private val userService: UserApi,
    private val commentService: CommentApi
) {
    suspend fun getFeedItems(): List<FeedItem> = coroutineScope {
        val articlesDeferred = async { articleService.getArticles() }
        val usersDeferred = async { userService.getAuthors() }
        val countsDeferred = async { commentService.getCommentCounts() }

        // Parallel fetch — all three requests in flight simultaneously
        val articles = articlesDeferred.await()
        val users = usersDeferred.await().associateBy { it.id }
        val counts = countsDeferred.await().associateBy { it.articleId }

        articles.map { article ->
            FeedItem(
                article = article,
                author = users[article.authorId],
                commentCount = counts[article.id]?.count ?: 0
            )
        }
    }
}

Caching Aggregated Responses

Cache the assembled response, not the individual service responses:

// Room entity matches the aggregated view, not individual service models
@Entity(tableName = "feed_items")
data class FeedItemEntity(
    @PrimaryKey val articleId: String,
    val title: String,
    val authorName: String,
    val authorAvatarUrl: String,
    val commentCount: Int,
    val cachedAt: Long = System.currentTimeMillis()
)

Key Takeaways

ApproachBest for
Direct service callsSimple apps with ≤ 2 services
BFFMobile team that controls their own API; screen-specific optimization
GraphQLApps with complex, varied data needs; teams that already use GraphQL on backend
Parallel coroutinesAggregating multiple endpoints without a BFF

YOUR LEARNING JOURNEY

0 of 177 available lessons completed

Progress saved in this browser. No account needed.
Integrating with Microservices & Graph Layers | Android System Design | Android Engineers