Case Studies Projects About Blog Let's Talk Case Studies Projects About Blog

Designing Map Integrations for Change

SmartThingsFindArchitectureGoogle MapsNaver Maps

While working on SmartThings Find for iOS, one of my architectural responsibilities was designing how the application would support multiple map providers.

Before starting, I examined a similar implementation from another project. Looking through its commit history, I noticed something concerning: a single change sometimes required modifying two separate implementations, one for each map provider.

It worked, but I wanted to avoid introducing the same maintenance problem into our application.

A shared abstraction for different providers

Most map SDKs provide similar capabilities, such as displaying markers, drawing polylines, controlling the camera, and handling user interactions. However, their APIs and implementation details can differ significantly.

Instead of integrating each SDK independently, I introduced a shared abstraction between the application logic and map providers.

I designed a common map wrapper, along with base classes for maps, markers, and polylines. Each provider had its own implementation behind these shared boundaries.

Since our application was built with Lit, I also wanted to maintain a consistent component model.

Google Maps already provides official Web Components, such as <gmp-map> and <gmp-advanced-marker>. Naver Maps doesn't provide equivalent components, so I built custom Lit Web Components that wrapped its JavaScript SDK and exposed similar interfaces.

Polylines required a different approach. Since Google doesn't provide an equivalent Polyline Web Component, I handled them through a shared JavaScript abstraction instead.

The goal was to provide consistent interfaces without forcing every feature into the same implementation pattern.

This allowed the application to work with either provider while keeping their SDK specific details separate.

Find Maps Simplified Diagram

Preparing for future changes

Another consideration was the possibility of introducing different map providers in the future.

Perhaps the product would expand into a region requiring another service, or an existing provider might change its pricing or availability.

These weren't confirmed requirements, but they were realistic possibilities.

By keeping provider specific behavior behind common abstractions, I wanted to reduce how much application code would need to change if we ever introduced or replaced a provider.

Of course, abstractions come with tradeoffs. They introduce additional complexity, and different SDKs don't always support exactly the same capabilities.

The challenge is finding the right level of abstraction. Too little creates duplication, while too much can make the code harder to understand and maintain.

I focused on the capabilities our application actually needed rather than anticipating every possible feature of a future map provider.

Looking back

As the project progressed, we encountered changing requirements and unexpected engineering challenges. Having clear architectural boundaries helped us accommodate changes without repeatedly restructuring the application.

I believe this contributed to our ability to deliver the project on schedule, alongside the team's effort, testing, and coordination.

This experience reinforced something I've learned throughout my career: good architecture should make changes easier to manage.

We don't need to predict every future requirement or build abstractions for every possibility.

Sometimes, identifying responsibilities that should remain separate is already a valuable investment.

And in this project, that investment was worth making.