Separation Of Concerns Into Services
August 8, 2026 · View on GitHub
We have built apps at/for companies
that went full "microservices" e.g. Uber.
We found the complexity killed effectiveness
because almost any change required shipping code to 3 or more different repos.
However we do see the benefits of separate services for specific functionality.
-
Web Appowns all Business Logic which connects to all other services e.g:Authwhenever a specific function e.g:rolesorpermissionsquery is required. All “Transaction data” (owned by the business) references auser_idowned byAuthservice. TheWeb Appis the primary interface the people (and other apps/agents) interact with, and mediates access to the services layer. If theAppneeds to send a "Payment Confirmation" email, it sends that request toCommsand receives amessage_id. -
Authentication/Authorization functions including storage of personally identifiable information such as social security (NIF), Phone Number, email, (hashed) password, IP address and location data, should be stored on a logically separate and highly secured database instance which regular engineers never need to touch. This adds much needed security for mitigating data breaches.
-
Analytics & Logging - the lifeblood of any business - is a separate service with it's own distinct backend. We recommend running an instance of plausible.io or paying for the hosted service (€19/month), it includes all of the core features of
Google Analyticswithout the privacy concerns of leaking personal data. TreatingAnalyticsdata as logically separate fromBusiness Logicis best practice because it is not the core functionality of the business so the service should not impact response times of requests for a booking. -
Comms Service- We deploy a separateComms Servicefor handling sending email, SMS and notifications. This uses 3rd-party service such asAWSorTwilioto handle the delivery. Having Comms separate from App means regular engineers never have access to theAWSAPI Keys and cannot accidentally leak them (huge headache/cost). The App code just calls the service with a simple request: “Send Booking Confirmed email touser_id:123”. -
Payments Servicehandles all payment requests by re-routing them to thePayment Provider, in our caseStripe. Having this service separate is again to avoid engineers having access to API Keys or any sort of sensitive payment data. -
APIrequests (JSON+WebSockets) are handled by theWeb Appwithout incurring additional complexity; the same endpoints that renderHTMLcan renderJSON. The sameAuthandWebSocketsused by theWeb Appcan be consumed natively by theMobile App. When the traffic volume justifies spinning up a dedicated API server, it’s simply an additional instance without any incremental devops overhead.
I attempted to create the
diagram using Mermaid:
---
config:
theme: 'base'
themeVariables:
primaryColor: '#CEF4EE'
primaryTextColor: '#556A72'
lineColor: '#F8B229'
secondaryColor: '#006100'
flowchart:
defaultRenderer: "elk"
---
graph BT
WebClient["`<h3>Web Client</h3>(All Devices)`"]
MobileApp["<h4>Native Mobile App</h4><br/>(iOS / Android)"]
WebClient <--> WebApp
MobileApp <--> WebApp
WebApp["<h2>Phoenix Web App</h2><br /> Handles all Web/API requests involving <b>Business Logic</b>. Serves HTTP, JSON and WebSocket Requests<br/>"]
WebApp --> Auth["<h2>Auth</h2><br />
Handles Authentication, Authorization, Permissions via Role-based Access Control"]
Auth --> authDb[(Auth DB)]
authDb --> authDb2[(Auth Backup)]
Auth <--> Comms
WebApp <--> Comms["<h2>Comms Service</h2><br /><b>3<sup>rd</sup> Party Services Send Email, SMS & Notifications</b>.
Tracks deliverability, open & click-through rate. Dashboards display stats + insights."]
Comms <--> AWS["<h3>AWS SES/SNS</h3> Sends Email, SMS and Mobile Notifications"]
Comms --> comDb[(Comms DB)]
comDb --> backDb
WebApp <--> Analytics["<h2>Analytics + Logs</h2> Captures anonymous web/mobile analytics events and logs. Renders metrics dashboards."]
Analytics --> anaDb[(Analytics <br />+ Logs DB)]
anaDb --> backDb
WebApp --> mainDb[("<h2>Main DB</h2>")]
mainDb --> backDb[("<h2>Backup DB</h2> Read-only Replica")]
classDef entryPoint stroke:#818cf8,fill:#eef2ff
classDef WebApp stroke:#2dd4bf,fill:#f0fdfa
classDef process stroke:#a78bfa,fill:#f5f3ff
classDef tool stroke:#fb923c,fill:#fff7ed
classDef db stroke:#336791,fill:#e3edf5
classDef aws stroke:#252F3E,fill:#ffc267
class WebClient,MobileApp entryPoint
class WebApp WebApp
class Auth process
class Comms,Analytics tool
class authDb,authDb2,mainDb,backDb,anaDb,comDb db
class AWS, aws
But sadly the renderer is non-deterministic (i.e. we can't control the layout or position of elements) See: dwyl/technology-stack#172