<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ankit Karan</title>
    <description>The latest articles on DEV Community by Ankit Karan (@ankitkaran99).</description>
    <link>https://dev.to/ankitkaran99</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4191950%2Fcaff0d1e-7427-42f4-b3dd-f7f297e4ecfa.jpg</url>
      <title>DEV Community: Ankit Karan</title>
      <link>https://dev.to/ankitkaran99</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ankitkaran99"/>
    <language>en</language>
    <item>
      <title>Building a Cloud-Native School ERP: Architecture, Real-Time Fleet Tracking, and Data Integrity</title>
      <dc:creator>Ankit Karan</dc:creator>
      <pubDate>Sun, 18 Oct 2026 11:08:19 +0000</pubDate>
      <link>https://dev.to/ankitkaran99/building-a-cloud-native-school-erp-architecture-real-time-fleet-tracking-and-data-integrity-3819</link>
      <guid>https://dev.to/ankitkaran99/building-a-cloud-native-school-erp-architecture-real-time-fleet-tracking-and-data-integrity-3819</guid>
      <description>&lt;p&gt;Educational institutions frequently juggle an unmanageable software stack: a legacy desktop tool for accounting, spreadsheets for grades, fragmented messaging apps for parent communication, and disconnected standalone hardware for attendance. &lt;/p&gt;

&lt;p&gt;When administrative tools operate in silos, data bottlenecks arise. Report cards require days of manual exports, fee reconciliation involves cross-referencing physical receipts, and parents are left waiting for fragmented updates.&lt;/p&gt;

&lt;p&gt;At &lt;strong&gt;&lt;a href="https://acadhex.com" rel="noopener noreferrer"&gt;AcadHex&lt;/a&gt;&lt;/strong&gt;, we approached this problem as a systems integration and distributed architecture challenge. Below is a look into how we architected a unified, multi-tenant Cloud School ERP designed to automate campus workflows in real time.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Core Architectural Pillars
&lt;/h2&gt;

&lt;p&gt;A production-grade School Management System handles wide variations in traffic: minimal load during instructional hours, sudden spikes during morning gate check-ins, and heavy concurrent sessions during report-card releases or fee deadlines.&lt;/p&gt;

&lt;p&gt;To balance these patterns, our platform relies on three key design principles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strict Multi-Tenancy &amp;amp; Data Isolation:&lt;/strong&gt; Each school network or academy operates with strict logical data isolation. Tenant resolution occurs at the gateway layer via subdomain routing, with database connection pooling ensuring isolated tenant contexts while sharing compute resources efficiently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event-Driven Communication:&lt;/strong&gt; Critical campus actions—such as biometric check-ins or fee receipts—emit domain events rather than triggering synchronous write chains.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hardware-Agnostic Ingestion:&lt;/strong&gt; Campuses deploy diverse hardware setups. Our API abstracts physical hardware (biometric terminals, GPS units) behind standardized webhook and telemetry ingestion pipelines.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                +---------------------------------------+
                |          API Gateway &amp;amp; Routing        |
                |    (Tenant Resolution via Subdomain)   |
                +-------------------+-------------------+
                                    |
          +-------------------------+-------------------------+
          |                                                   |
+---------v---------+                               +---------v---------+
|  Real-Time Fleet  |                               |   Core Business   |
| Telemetry Service |                               |   Domain APIs     |
+---------+---------+                               +---------+---------+
          | (MQTT/UDP)                                        | (REST/RPC)
+---------v---------+                               +---------v---------+
| Hardware/Vehicles |                               | PostgreSQL / Read |
|   (GPS Devices)   |                               |      Replicas     |
+-------------------+                               +-------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. Ingesting Live GPS Fleet Telemetry at Scale
&lt;/h2&gt;

&lt;p&gt;Managing student transportation demands reliable, low-latency telemetry. Traditional HTTP polling introduces unnecessary overhead for vehicle tracking updates transmitted every 3 to 5 seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Telemetry Pipeline
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Device Packets:&lt;/strong&gt; Onboard vehicle GPS trackers broadcast lightweight UDP/MQTT packets containing coordinates, speed, ignition status, and vehicle telemetry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ingestion Layer:&lt;/strong&gt; A Node.js worker layer decodes incoming binary/NMEA data payloads and validates device signatures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Geofencing &amp;amp; Event Triggers:&lt;/strong&gt; Ingested coordinates pass through an in-memory spatial index. When a vehicle crosses a defined perimeter (e.g., &lt;em&gt;Approaching Stop: Sector 4&lt;/em&gt;), the system triggers parent notification events.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Broadcast via WebSockets:&lt;/strong&gt; Real-time updates are published via Redis Pub/Sub directly to authenticated client dashboard sockets.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;VehicleTelemetryPacket&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;deviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;speedKmh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Ingestion validation handler&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processTelemetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;packet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;VehicleTelemetryPacket&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;deviceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;longitude&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;packet&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// 1. Update latest vehicle state in cache&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;geoadd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`fleet:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deviceId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Publish to authenticated active web/mobile listeners&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pubsub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`fleet:updates:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;packet&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Evaluate active route geofences asynchronously&lt;/span&gt;
  &lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;evaluate-geofence&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deviceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;longitude&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Financial Consistency in Fee Automation
&lt;/h2&gt;

&lt;p&gt;Automated fee collection requires strict consistency. Distributed transactions can fail midway if parent payments, fee concessions, ledger writes, and receipt generation are not handled within transactional boundaries.&lt;/p&gt;

&lt;p&gt;We implement atomic transactions with idempotency keys across all collection endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Atomic ledger write pattern for fee processing&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- 1. Lock invoice record to prevent concurrent double-collection&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;balance_due&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;student_invoices&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'inv_98234'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'PENDING'&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- 2. Register successful transaction entry&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;payment_transactions&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;invoice_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount_paid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gateway_ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'txn_44102'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'inv_98234'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'pay_tok_live_891'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'SUCCESS'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- 3. Reconcile balance and update invoice status&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;student_invoices&lt;/span&gt; 
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;balance_due&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;balance_due&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;15000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;balance_due&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;15000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'SETTLED'&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="s1"&gt;'PARTIAL'&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'inv_98234'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Idempotency tokens on payment callbacks prevent duplicate receipts or balance miscalculations if a payment gateway retries a webhook during network fluctuations.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Hardware Integration: Biometric Attendance Pipelines
&lt;/h2&gt;

&lt;p&gt;Connecting campus biometric hardware to cloud infrastructure presents practical challenges: school-level devices frequently experience intermittent offline states.&lt;/p&gt;

&lt;p&gt;To prevent missing punch records, biometric terminals send batches using local storage replay:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every punch is assigned a monotonically increasing sequence ID per device.&lt;/li&gt;
&lt;li&gt;Devices buffer attendance records locally during network outages and flush payloads sequentially upon reconnection.&lt;/li&gt;
&lt;li&gt;The API ingestion service deduplicates punches against a composite key &lt;code&gt;(device_id, student_rfid, timestamp_minute)&lt;/code&gt; to guarantee zero duplicate markings while maintaining reliable attendance records.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  5. System Design Takeaways
&lt;/h2&gt;

&lt;p&gt;Building software for physical campus environments requires designing for edge-case resilience:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Expect Intermittent Connectivity:&lt;/strong&gt; Hardware bridges (turnstiles, vehicle tracking) must have built-in queuing and retry guarantees.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decouple Event Feeds from Query APIs:&lt;/strong&gt; Separating live tracking feeds and heavy analytics queries prevents dashboard latency during peak morning check-ins.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prioritize Auditability:&lt;/strong&gt; Administrative actions—grading adjustments, fee waivers, schedule updates—require immutable audit trails to maintain institutional trust.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By moving schools away from disconnected spreadsheets and legacy systems toward event-driven cloud platforms, administrators gain real-time oversight while parents stay informed.&lt;/p&gt;

</description>
      <category>edtech</category>
      <category>architecture</category>
      <category>cloud</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
