پرش به محتوای اصلی
مهندسی نرم‌افزار

ساخت یک پلتفرم GPS: از دستگاه تا نقشه لحظه‌ای

نگاهی مهندسی به معماری یک پلتفرم GPS؛ از ارتباط با دستگاه و پردازش تله‌متری تا ذخیره‌سازی داده، پردازش مکانی، سیستم‌های لحظه‌ای و تجربه نقشه.

تاریخ انتشار
زمان مطالعه
۱۰ دقیقه مطالعه
  • GPS
  • Backend
  • Go
  • NestJS
  • PostgreSQL
  • PostGIS
  • Redis
  • MapLibre

یک اپلیکیشن ردیابی GPS ممکن است در نگاه اول بسیار ساده به نظر برسد.

یک دستگاه موقعیت خود را ارسال می‌کند، بک‌اند آن را دریافت می‌کند و کاربر خودرو را روی نقشه می‌بیند.

اما این توضیح بخش بزرگی از پیچیدگی مهندسی را پنهان می‌کند.

یک پلتفرم GPS در محیط واقعی باید دستگاه‌های فیزیکی، شبکه‌های ناپایدار، پروتکل‌های دستگاه، سرویس‌های بک‌اند، پایگاه‌های داده، پردازش لحظه‌ای، عملیات مکانی، احراز هویت، مجوزها و رابط‌های کاربری را به یکدیگر متصل کند.

بخش دشوار این نیست که هر کدام از این اجزا را به‌تنهایی اجرا کنیم.

بخش دشوار این است که همه آن‌ها مانند یک سیستم منسجم و یکپارچه رفتار کنند.

سیستم از دستگاه شروع می‌شود

یک ردیاب GPS اساساً با یک کلاینت معمولی وب متفاوت است.

مرورگر یا اپلیکیشن موبایل معمولاً چرخه عمر نسبتاً قابل پیش‌بینی دارد. اما یک ردیاب فیزیکی این‌طور نیست.

ممکن است:

  • اتصال شبکه خود را از دست بدهد
  • پس از چند دقیقه دوباره متصل شود
  • بسته‌های ناقص ارسال کند
  • مختصات قدیمی ارسال کند
  • تله‌متری را با فواصل زمانی متفاوت ارسال کند
  • همچنان متصل باشد اما برای مدتی داده مفیدی تولید نکند
  • از مسیر شبکه‌ای متفاوتی دوباره متصل شود

بنابراین بک‌اند نمی‌تواند همه چیز را صرفاً با مفهومی مانند زیر مدل کند:

connected = true

یک پلتفرم مناسب باید چند مفهوم مستقل را مدل کند.

برای مثال:

ثبت دستگاه
        |
        v
احراز مجاز بودن دستگاه
        |
        v
اتصال شبکه
        |
        v
دریافت تله‌متری
        |
        v
فعالیت اخیر
        |
        v
وضعیت آنلاین در اپلیکیشن

این وضعیت‌ها به یکدیگر مرتبط هستند، اما یکسان نیستند.

یک دستگاه می‌تواند ثبت شده باشد، بدون اینکه متصل باشد.

می‌تواند متصل باشد، اما اخیراً هیچ نقطه GPS ارسال نکرده باشد.

همچنین ممکن است موقعیت جدیدی داشته باشد، در حالی که اتصال فعلی آن از قبل قطع شده است.

مشخص کردن این تفاوت‌ها در مراحل اولیه طراحی، از بسیاری از مشکلات بعدی جلوگیری می‌کند.

دنبال کردن یک نقطه GPS در سراسر پلتفرم

یکی از بهترین روش‌ها برای درک یک سیستم متصل به دستگاه، دنبال کردن یک نقطه GPS در تمام مسیر معماری است.

یک جریان ساده‌شده می‌تواند به شکل زیر باشد:

┌─────────────────┐
│   GPS Device    │
└────────┬────────┘
         │
         │ TCP / Protocol
         ▼
┌─────────────────┐
│ Device Server   │
│    Go / GT06    │
└────────┬────────┘
         │
         │ Telemetry
         ▼
┌─────────────────┐
│ Processing Layer │
└───────┬─────────┘
        │
        ├──────────────► Redis
        │
        ▼
┌─────────────────┐
│ Backend Services │
│     NestJS      │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│   PostgreSQL    │
│    PostGIS      │
│   TimescaleDB   │
└────────┬────────┘
         │
         ├──────────────► Web API
         │
         └──────────────► Mobile API
                              │
                              ▼
                       ┌─────────────┐
                       │ Map / Client │
                       └─────────────┘

هر لایه مسئولیت متفاوتی دارد.

سرور دستگاه پروتکل ارتباطی را می‌شناسد.

لایه پردازش، پیام‌های ورودی را به تله‌متری قابل استفاده تبدیل می‌کند.

بک‌اند دامنه و منطق کسب‌وکار اپلیکیشن را می‌شناسد.

پایگاه داده وضعیت پایدار و اطلاعات تاریخی را ذخیره می‌کند.

کلاینت نیز این وضعیت را به کاربر نمایش می‌دهد.

جدا نگه داشتن این مسئولیت‌ها باعث می‌شود نگهداری سیستم ساده‌تر شود.

ارتباط با دستگاه باید مرز مستقل خود را داشته باشد

پروتکل‌های دستگاه معمولاً نباید بخشی از منطق دامنه اپلیکیشن باشند.

یک parser پروتکل نباید لازم باشد بداند vendor چیست، کاربر چگونه احراز هویت می‌شود یا یک خودرو متعلق به کدام حساب است.

مسئولیت آن باید بسیار محدودتر باشد:

بایت‌های ورودی
      ↓
پردازش پروتکل
      ↓
پیام اعتبارسنجی‌شده
      ↓
تله‌متری نرمال‌شده

برای مثال، لایه پروتکل می‌تواند یک پیام اختصاصی دستگاه را به ساختاری نرمال‌شده تبدیل کند:

{
  imei: "868780070462120",
  latitude: 35.7165,
  longitude: 51.3907,
  speed: 42,
  course: 180,
  acc: true,
  recordedAt: "2026-09-24T10:20:00Z"
}

بخش‌های دیگر اپلیکیشن نباید لازم باشد بدانند این مقادیر چگونه روی سیم یا شبکه encode شده‌اند.

این جداسازی زمانی اهمیت بیشتری پیدا می‌کند که پلتفرم در آینده از چند پروتکل مختلف دستگاه پشتیبانی کند.

قبل از ساخت قابلیت‌ها، تله‌متری را نرمال کنید

دستگاه‌های GPS معمولاً مفاهیم مشابه را با نمایش‌های متفاوت و وابسته به پروتکل ارائه می‌کنند.

یک دستگاه ممکن است وضعیت ignition را به‌صورت یک bit ارسال کند.

دستگاه دیگری ممکن است از یک فیلد alarm استفاده کند.

دستگاه دیگری ممکن است یک مقدار وضعیت اختصاصی vendor داشته باشد.

بهتر است اپلیکیشن به‌جای انتقال تفاوت‌های پروتکلی در سراسر codebase، با مفاهیم نرمال‌شده کار کند.

برای مثال:

پیام وابسته به پروتکل
        ↓
Protocol Adapter
        ↓
تله‌متری نرمال‌شده
        ↓
سرویس‌های دامنه
        ↓
قابلیت‌های اپلیکیشن

این معماری اجازه می‌دهد قابلیت‌هایی مانند هشدارها، گزارش‌ها، نقشه‌ها و وضعیت خودرو بر اساس مفاهیم پایدار اپلیکیشن کار کنند.

همچنین باعث می‌شود تغییرات پروتکل به بخش‌های نامرتبط سیستم نشت نکنند.

انتخاب محل مناسب برای ذخیره داده

سیستم‌های GPS انواع مختلفی از داده تولید می‌کنند.

یک دسته، داده‌های نسبتاً پایدار اپلیکیشن است:

  • کاربران
  • فروشندگان
  • دستگاه‌ها
  • خودروها
  • مجوزها
  • تنظیمات

دسته دیگر، داده‌های مکانی است:

  • مختصات
  • مسیرها
  • ژئوفنس‌ها
  • روابط مکانی

دسته دیگر، تله‌متری وابسته به زمان است:

  • نقاط GPS
  • سرعت
  • وضعیت ignition
  • مقادیر سنسورها
  • فعالیت دستگاه

و در نهایت داده‌های عملیاتی کوتاه‌عمر وجود دارند:

  • فرمان‌ها
  • رویدادها
  • پیام‌های پردازشی
  • وضعیت موقت

برخورد یکسان با تمام این workloadها معمولاً سیستم را سخت‌تر از نظر عملیاتی می‌کند.

یک معماری سنجیده، هر workload را به زیرساختی می‌سپارد که با ویژگی‌های آن سازگار باشد.

برای مثال:

PostgreSQL
    ├── Users
    ├── Devices
    ├── Vehicles
    ├── Permissions
    └── Application state

PostGIS
    ├── Coordinates
    ├── Routes
    └── Geofences

TimescaleDB
    └── Time-series telemetry

Redis
    ├── Short-lived state
    ├── Events
    └── Service communication

اصل مهم تعداد تکنولوژی‌ها نیست.

اصل مهم این است که هر جزء زیرساختی مسئولیت مشخصی داشته باشد.

داده‌های مکانی معماری را تغییر می‌دهند

Latitude و longitude ممکن است فقط دو عدد به نظر برسند، اما وقتی محصول شروع به پرسیدن سؤالات مکانی می‌کند، عملیات معمول پایگاه داده دیگر کافی نیستند.

برای مثال:

  • آیا خودرو داخل یک ژئوفنس قرار دارد؟
  • کدام خودروها به یک موقعیت نزدیک هستند؟
  • خودرو چه مسافتی را طی کرده است؟
  • چه مسیری را طی کرده است؟
  • آیا از یک محدوده مشخص خارج شده است؟
  • کدام دستگاه‌ها در یک محدوده جغرافیایی فعال بوده‌اند؟

این‌ها عملیات مکانی هستند.

پایگاه داده‌ای که قابلیت‌های spatial داشته باشد، اجازه می‌دهد بخش قابل توجهی از این پردازش نزدیک به خود داده انجام شود.

برای مثال، به‌جای اینکه هزاران نقطه GPS را به اپلیکیشن منتقل کنیم و فاصله‌ها را به‌صورت دستی محاسبه کنیم، می‌توان از توابع مکانی پایگاه داده برای انجام این عملیات استفاده کرد.

این موضوع با افزایش حجم تله‌متری تاریخی اهمیت بیشتری پیدا می‌کند.

زمان به اندازه موقعیت اهمیت دارد

یک نقطه GPS بدون timestamp معنادار، ناقص است.

این دو نقطه را در نظر بگیرید:

35.7165, 51.3907

و:

35.7165, 51.3907
2026-09-24T10:20:00Z

نقطه دوم مشخص می‌کند خودرو چه زمانی در آن موقعیت بوده است.

این موضوع برای موارد زیر اهمیت دارد:

  • بازسازی مسیر
  • پخش تاریخچه
  • محاسبه مسافت
  • گزارش‌گیری
  • ارزیابی هشدارها
  • فعالیت دستگاه
  • شناسایی تله‌متری قدیمی

بنابراین در یک سیستم GPS تولیدی، موقعیت و زمان باید هر دو به‌عنوان بخش‌های اصلی مدل تله‌متری در نظر گرفته شوند.

لحظه‌ای بودن به این معنا نیست که همه چیز باید دائماً به‌روزرسانی شود

سیستم‌های real-time به‌راحتی می‌توانند زمانی که تمام اطلاعات با یک سطح اهمیت در نظر گرفته می‌شوند، بیش از حد پرهزینه شوند.

داده‌های مختلف به میزان تازگی متفاوتی نیاز دارند.

موقعیت فعلی خودرو ممکن است به‌روزرسانی‌های مکرر نیاز داشته باشد.

اما مسیرهای تاریخی چنین نیازی ندارند.

یک تغییر تنظیمات ممکن است فقط هر چند دقیقه یک بار اتفاق بیفتد.

یک گزارش ممکن است به‌صورت asynchronous تولید شود.

یک هشدار ممکن است نیازمند تحویل فوری باشد.

توجه صریح به freshness داده کمک می‌کند مشخص کنیم کجا واقعاً به زیرساخت real-time نیاز داریم.

یک مدل ذهنی مفید:

فرکانس بالا
    └── موقعیت لحظه‌ای

فرکانس متوسط
    └── وضعیت اتصال / وضعیت خودرو

مبتنی بر رویداد
    ├── هشدارها
    ├── فرمان‌ها
    └── تغییرات تنظیمات

پردازش غیرهمزمان
    ├── گزارش‌ها
    ├── تجمیع داده
    └── پردازش تاریخی

این رویکرد مانع از آن می‌شود که کل پلتفرم به سخت‌ترین workload موجود وابسته شود.

Redis مفید است، اما نباید به پایگاه داده تبدیل شود

Redis در سیستم‌های متصل به دستگاه بسیار کاربردی است.

می‌تواند برای موارد زیر استفاده شود:

  • وضعیت موقت
  • صف‌ها
  • رویدادها
  • ارتباط فرمان‌ها
  • cacheهای کوتاه‌عمر
  • هماهنگی بین سرویس‌ها

اما نباید به‌صورت خودکار source of truth همه چیز شود.

وضعیت پایدار اپلیکیشن معمولاً باید محل ذخیره‌سازی پایدار خودش را داشته باشد.

یک تفکیک مفهومی مفید:

PostgreSQL
    = آنچه سیستم می‌داند

Redis
    = آنچه سیستم در حال انجام آن است

معماری دقیق می‌تواند متفاوت باشد، اما حفظ این تفکیک مفهومی از پیچیدگی عملیاتی غیرضروری جلوگیری می‌کند.

نقشه فقط نمایش نهایی سیستم است

نقشه معمولاً قابل مشاهده‌ترین بخش یک محصول GPS است.

در عین حال، یکی از اولین جاهایی است که مشکلات معماری خود را نشان می‌دهند.

اگر تله‌متری با فاصله‌های نامنظم دریافت شود، markerها ممکن است پرش داشته باشند.

اگر state با فرکانس بیش از حد به‌روزرسانی شود، رابط کاربری ممکن است هزینه غیرضروری متحمل شود.

اگر منطق دنبال کردن دوربین به‌درستی طراحی نشده باشد، کاربر کنترل خود بر نقشه را از دست می‌دهد.

اگر داده تاریخی و live با یکدیگر ترکیب شوند، مدیریت rendering دشوار می‌شود.

بنابراین یک معماری مناسب برای نقشه باید چند مسئولیت را از هم جدا کند:

Telemetry
    ↓
Application state
    ↓
Map state
    ├── Live marker
    ├── Camera
    ├── Route
    ├── Geofence
    └── User interaction

نقشه باید وضعیت اپلیکیشن را نمایش دهد.

نقشه نباید مالک کل مدل state اپلیکیشن باشد.

حرکت دوربین یک مسئله محصول است

دنبال کردن یک خودرو در نگاه اول ساده به نظر می‌رسد:

نقطه GPS جدید
      ↓
حرکت دوربین

اما زمانی که کاربر در حال بررسی دستی نقشه است، این رفتار می‌تواند آزاردهنده شود.

مدل بهتر، حرکت ناشی از اپلیکیشن و حرکت ناشی از کاربر را از یکدیگر جدا می‌کند.

برای مثال:

حالت دنبال کردن
    ↓
دریافت GPS جدید
    ↓
حرکت marker
    ↓
حرکت دوربین

کاربر نقشه را جابه‌جا می‌کند
    ↓
غیرفعال شدن دنبال کردن خودکار دوربین
    ↓
ادامه دریافت تله‌متری

خودرو باید حتی زمانی که دوربین دیگر آن را دنبال نمی‌کند، همچنان به‌روزرسانی شود.

این تفاوت از نظر فنی کوچک است، اما از نظر تجربه کاربری اهمیت زیادی دارد.

احراز هویت فقط بخشی از کنترل دسترسی است

یک پلتفرم متصل به دستگاه معمولاً چندین لایه authorization دارد.

دانستن هویت کاربر به‌تنهایی مشخص نمی‌کند که او به چه چیزهایی دسترسی دارد.

هر درخواست ممکن است نیاز داشته باشد به سؤالات زیر پاسخ دهد:

کاربر چه کسی است؟
        ↓
چه نقشی دارد؟
        ↓
متعلق به کدام vendor است؟
        ↓
به کدام دستگاه‌ها دسترسی دارد؟
        ↓
چه عملیاتی برای او مجاز است؟

این موضوع برای عملیات‌هایی مانند موارد زیر اهمیت ویژه‌ای دارد:

  • مشاهده تاریخچه موقعیت
  • مشاهده هشدارها
  • مدیریت ژئوفنس‌ها
  • ارسال فرمان‌های راه دور
  • تغییر تنظیمات دستگاه
  • اشتراک‌گذاری دسترسی به دستگاه

بنابراین authorization باید در مرز دامنه و backend enforce شود و نباید فقط به مخفی کردن قابلیت‌ها در frontend متکی باشد.

فرمان‌های راه دور مسئله متفاوتی هستند

خواندن تله‌متری و ارسال یک فرمان دو عملیات کاملاً متفاوت هستند.

تله‌متری معمولاً در این مسیر جریان دارد:

Device → Platform → Client

اما فرمان راه دور در جهت مخالف حرکت می‌کند:

Client
  ↓
Backend
  ↓
Command queue
  ↓
Device connection
  ↓
Physical device
  ↓
Response
  ↓
Backend
  ↓
Client

بنابراین سیستم باید موارد زیر را مدیریت کند:

  • شناسه فرمان
  • authorization
  • تحویل فرمان
  • timeout
  • در دسترس بودن دستگاه
  • retry
  • پاسخ دستگاه
  • خطا
  • بازخورد به کاربر

یک فرمان راه دور نباید مانند یک HTTP request ساده در نظر گرفته شود که بلافاصله وضعیت یک دستگاه فیزیکی را تغییر می‌دهد.

ممکن است در همان لحظه شبکه بین اپلیکیشن و دستگاه در دسترس نباشد.

شکست بخشی از معماری است

سیستم‌های توزیع‌شده باید با فرض رخ دادن failure طراحی شوند، نه با فرض اینکه failure یک اتفاق استثنایی است.

یک دستگاه قطع خواهد شد.

یک درخواست شبکه شکست خواهد خورد.

یک سرویس restart خواهد شد.

یک پیام با تأخیر خواهد رسید.

یک query پایگاه داده گاهی بیشتر از حد انتظار زمان خواهد برد.

یک کاربر ممکن است دقیقاً در بدترین لحظه یک عملیات را اجرا کند.

هدف این نیست که تمام خطاها را حذف کنیم.

هدف این است که خطاها قابل فهم و قابل بازیابی باشند.

در یک پلتفرم GPS، این موضوع به معنای توجه صریح به موارد زیر است:

  • timeout
  • retry
  • وضعیت قدیمی
  • idempotency
  • بازیابی اتصال
  • انقضای فرمان
  • اعتبارسنجی داده
  • observability
  • مرزهای authorization

این موارد جزئیات فرعی پیاده‌سازی نیستند.

آن‌ها بخشی از خود معماری هستند.

مرزها را قابل فهم نگه دارید

بعد از بررسی لایه‌های مختلف، اصل معماری مرکزی نسبتاً ساده می‌شود:

هر بخش از سیستم باید مسئولیت مشخصی داشته باشد.

سرور دستگاه باید ارتباط با دستگاه را بشناسد.

بک‌اند باید دامنه و منطق اپلیکیشن را بشناسد.

پایگاه داده باید وضعیت پایدار را به‌صورت کارآمد ذخیره و query کند.

لایه real-time باید فقط در جاهایی که واقعاً به رفتار لحظه‌ای نیاز است، مسئول ارتباط باشد.

Frontend باید وضعیت اپلیکیشن را نمایش دهد و تجربه کاربری را ارائه کند.

وقتی این مرزها شفاف باقی بمانند، فناوری‌های منفرد می‌توانند تغییر کنند بدون اینکه مجبور شویم کل سیستم را همراه با آن‌ها تغییر دهیم.

جمع‌بندی

ساخت یک پلتفرم GPS در نهایت تمرینی برای مدیریت state توزیع‌شده است.

در یک محصول واحد، دستگاه‌های فیزیکی، شبکه‌های ناپایدار، سرویس‌های بک‌اند، پایگاه‌های داده، ارتباطات لحظه‌ای، نقشه‌ها، کاربران و مجوزها همگی درگیر هستند.

چالش اصلی این است که این اجزای مستقل مانند یک سیستم منسجم رفتار کنند.

برای رسیدن به این هدف، صرفاً انتخاب یک framework کافی نیست.

باید جریان داده را درک کرد، مرزها را مشخص کرد، state را با دقت مدل کرد، failure را به‌صورت صریح مدیریت کرد و مشخص کرد که رفتار لحظه‌ای واقعاً در کدام بخش‌ها ارزشمند است.

تکنولوژی‌ها مهم هستند.

اما معماری مهم‌تر است.


این نسخه را می‌توانید مستقیماً در `src/content/blog/fa/building-a-gps-platform.mdx` قرار دهید.