Digital Transformation
Building Scalable Digital Platforms for African Businesses
Offline-first design, mobile money, multi-currency ledgers, performance budgets and multi-tenancy: the architecture behind platforms built for African markets.
- By
- Gwavo Technologies Engineering
- Published
- Reading time
- 9 min

Building software for businesses across Africa is not a matter of taking a platform designed elsewhere and translating its interface. The constraints are different, and so are the opportunities: intermittent connectivity, mobile-first customers, payment rails built on mobile money rather than cards, economies that operate in more than one currency, and regulatory frameworks that are evolving quickly. Treated as edge cases, these conditions produce fragile systems. Treated as first-class design inputs, they produce platforms that are robust everywhere, including in markets where infrastructure is more forgiving.
This article sets out the architectural principles we apply when designing commerce, merchant operations and logistics software for these conditions, and the trade-offs that come with each.
Start from real operating conditions
Most architecture failures begin with an optimistic picture of the environment. Before choosing a stack, write down the conditions the system must survive:
- Connectivity that drops for minutes or hours, fluctuates between 4G, 3G and 2G within a single journey, and is often metered, so every megabyte has a cost for the user.
- Devices that are frequently entry-level Android phones with limited memory and storage, shared between staff or running an older operating system.
- Power that is not guaranteed, which means phones, tablets and point-of-sale terminals can shut down mid-transaction.
- Payments that rely heavily on mobile money and cash, with card acceptance far from universal.
- Currencies that coexist. In the Democratic Republic of the Congo, for example, prices, salaries and supplier invoices are routinely expressed in both US dollars and Congolese francs.
None of this is exotic. It is the baseline, and a platform that handles it gracefully will also behave better on a congested network in any capital city.
Turn each condition into a testable requirement. "Works offline" is vague. "A cashier can record sales for a full shift without connectivity and synchronize them without duplicates when the network returns" is something a team can design, build and test against.
Offline-first as an architectural choice
Offline support cannot be bolted on late. It changes where the truth lives, how identifiers are generated and how conflicts are resolved.
Make the local store the primary interface
In an offline-first client, the user interface reads from and writes to a local database (SQLite on native mobile, IndexedDB in the browser) rather than calling the network directly. A synchronization layer moves changes between the local store and the server in the background. The experience stays fast and predictable regardless of signal strength.
Generate identifiers on the client
If records only receive an identifier from the server, a device cannot create related data while offline: an order cannot reference a customer created five minutes earlier. Use client-generated, globally unique identifiers such as UUIDv7 or ULID, which are also time-ordered and index-friendly. Combined with idempotent server endpoints, they make retries safe, because sending the same change twice has the same effect as sending it once.
Use an outbox and an explicit sync protocol
Record every local mutation in an outbox table with its operation, payload and timestamp. A sync worker drains the outbox in order, marks entries as acknowledged only once the server confirms them, and pulls remote changes using a server-issued cursor or version number, never a vague notion of "everything since I last synced".
Decide conflict rules per domain
There is no universal conflict strategy. Last-write-wins is acceptable for a customer’s display name; it is dangerous for stock levels or account balances. Model inventory movements and payments as append-only events ("sold two units", "received ten units") rather than overwriting a quantity, so that concurrent changes from several devices merge naturally. Reserve server-side arbitration for invariants that genuinely need a single authority, such as preventing a voucher from being redeemed twice, and design the interface to explain clearly when an action recorded offline is later rejected.
Browser support for background synchronization also varies: the Background Sync API is not available in every browser engine, so web clients should sync opportunistically whenever the application is open and connectivity returns.
Payments: mobile money and multi-currency
Mobile money services such as M-Pesa, Orange Money and Airtel Money are central to commerce in much of the continent. Integrating them well requires a different mental model from card payments.
Treat every payment as an asynchronous state machine
A typical mobile money collection sends an authorization prompt to the customer’s phone and confirms the outcome later through a callback. The customer may approve, decline, ignore the prompt or lose coverage. Model payments explicitly, for example as initiated, pending, succeeded, failed and expired, and never assume the callback will arrive. A reconciliation job should query the provider for payments stuck in a pending state and match every transaction against the provider’s own records and settlement reports.
Isolate providers behind a payment abstraction
Each operator and aggregator has its own API, error codes, limits and settlement cycle, and all of these change over time. Put each integration behind an internal interface with a normalized status model, so that adding a provider does not ripple through order, invoicing and accounting code.
Represent money correctly
- Store amounts as integers in minor units together with an ISO 4217 currency code (
USD,CDF), never as floating-point numbers. - Never add amounts in different currencies without an explicit conversion.
- When converting, persist the rate used, its source and its timestamp with the transaction, so that any figure can be reproduced during an audit.
- Keep a double-entry ledger for balances such as merchant wallets or customer credit, rather than a mutable balance column.
- Allow prices to be defined per currency where the business requires it, instead of deriving one from the other at display time.
These rules feel pedantic until the first month-end close, when finance asks why two reports disagree by a few francs.
Performance budgets for constrained networks
Performance is a product decision with a direct cost to users on metered data. Set an explicit budget and enforce it in continuous integration rather than treating speed as an aspiration.
| Dimension | What to constrain |
|---|---|
| JavaScript | Compressed size of the code required for the first interaction |
| Images | Modern formats such as AVIF or WebP, responsive sizes, lazy loading below the fold |
| Round trips | Requests on the critical path, since latency often matters more than bandwidth |
| API payloads | Field selection, pagination and compression on list endpoints |
| Devices | Responsiveness on an entry-level Android phone, not only on a developer laptop |
Useful techniques include server-side rendering for the first view, Brotli compression, HTTP caching with validators such as ETag, delta synchronization instead of full refreshes, and a service worker that serves the application shell from cache. Test with network throttling and on real low-end hardware: emulators running on fast machines hide exactly the problems that matter.
Multi-tenant by design
Platforms that serve many merchants, branches or partner organizations should be multi-tenant from the first schema. Retrofitting tenant isolation is one of the most expensive refactors a team can face. Three models are common:
- Shared schema with a tenant identifier on every row. The most cost-efficient and simplest to operate. Enforce isolation in the database itself, for instance with PostgreSQL row-level security, rather than trusting every query to remember a filter.
- Schema per tenant. Stronger logical separation and simpler per-tenant backup and restore, at the cost of more complex migrations.
- Database per tenant. The strongest isolation and the clearest answer to data residency requirements, with the highest operational overhead.
Many platforms combine them: a shared model for most customers and dedicated databases for those with regulatory or contractual constraints. Whatever the model, make the tenant context explicit throughout the stack, in access tokens, logs, metrics, background jobs and cache keys, and apply per-tenant rate limits so that one busy customer cannot degrade service for everyone else.
Scaling progressively
Early in a product’s life, a microservices architecture usually adds more operational burden than it removes. A well-structured modular monolith is a better starting point: one deployable unit, with strict internal boundaries between domains such as catalog, orders, payments, inventory and delivery.
Make those boundaries real:
- Each module owns its tables; other modules reach its data only through its public interface.
- Dependency rules are enforced automatically with architecture tests or lint rules, not only in code review.
- Modules that should stay loosely coupled communicate through domain events, published with the transactional outbox pattern so that an event is emitted if and only if its transaction commits.
Extract a module into a separate service when there is a concrete reason: it must scale independently, it has a different availability target, it needs its own release cadence, or a separate team owns it. Payment callbacks and delivery tracking, for instance, often have very different load profiles from back-office reporting. Because the boundary already exists, extraction becomes an infrastructure change rather than a rewrite.
Observability and data residency
Observe the whole journey
When users are on unreliable networks, many failures happen outside your servers. Instrument both sides:
- Adopt OpenTelemetry for traces, metrics and logs, and propagate a correlation identifier from the client through every service and provider call.
- Buffer client-side telemetry and synchronize it when connectivity returns, just like business data, including sync queue depth and the age of the oldest unsynchronized change.
- Define service level objectives around user outcomes, such as payment confirmation time or sync success rate, rather than server CPU usage.
- Monitor every third-party dependency, payment providers above all, so that you can tell a provider outage from a defect in your own code.
Plan for data residency early
Data protection and localization rules differ across African countries and sectors, and they continue to evolve. Financial services, health and public-sector data in particular may be subject to requirements on where data is stored and who may access it. Rather than betting on a single answer, design for choice:
- Classify data by sensitivity from the start.
- Manage infrastructure as code, so that the platform can be deployed in a specific region or on local infrastructure when required.
- Know which subprocessors handle which data, including analytics, logging and support tools.
- Take legal advice for each market. Architecture can enable compliance; it cannot substitute for it.
A checklist for technical leaders
Before committing to a platform architecture, make sure your team can answer yes to each of these questions:
- Have operating conditions (connectivity, devices, power, payments and currencies) been written down as testable requirements?
- Can core workflows continue offline, with client-generated identifiers and idempotent synchronization?
- Are conflict rules defined per domain, with append-only models for stock and money?
- Is every payment modeled as a state machine and reconciled against provider records?
- Are amounts stored as integer minor units with an explicit currency and recorded exchange rates?
- Is a performance budget enforced in CI and verified on low-end devices?
- Is tenant isolation enforced by the database and visible in every log and metric?
- Are module boundaries inside the monolith enforced automatically?
- Can a failed payment or sync be traced end to end?
- Can the platform be deployed in another region if a regulator or customer requires it?
None of these practices is unique to Africa. What is distinctive is how quickly the cost of skipping them becomes visible. Platforms engineered for these conditions tend to be more resilient, cheaper to operate and easier to extend into new markets, which is precisely what businesses with continental ambitions need.


