0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

BLEの分散ネットワークとニュースアプリの違いを「照明ON/OFF」で理解する

0
Posted at

BLEの分散ネットワークとニュースアプリの違いを「照明ON/OFF」で理解する

BLE(Bluetooth Low Energy)のような分散ネットワークは、通常のWebアプリとはまったく違う前提で設計する必要がある。

この記事では「照明のON/OFF」を例にして、ニュースアプリとの違いをCS的に整理する。


想定するシステム

BLEネットワーク(分散)

ノード同士が直接通信する
中央サーバーがない
通信が届かない、遅れる、重複することがある

ニュースアプリ(中央集権)

サーバーが状態を一元管理する
クライアントはサーバーに問い合わせる
サーバー側の状態が正とみなされる

1. 通信モデルの違い

BLE→照明ONが届くとは限らない

BLEでは、スマホから「照明ON」を送っても、すべての照明に届くとは限らない。

起きること:

照明AだけONになる
照明BはOFFのまま
数秒後に遅れて届く
同じメッセージが複数回届く

つまり、通信は「成功する前提」ではなく「壊れる前提」で設計する。

ニュースアプリ→リクエストすればレスポンスが返る

ニュースアプリでは、基本的にはサーバーにリクエストを送り、レスポンスを受け取る。

もちろんタイムアウトやエラーはあるが、BLEに比べると通信モデルは安定している。

2. 状態管理の違い

BLE→状態がズレる

BLEネットワークでは、各ノードがそれぞれ状態を持つ。

そのため、一時的に次のような状態になる。

照明A: ON 照明B: OFF

これは異常ではなく、分散ネットワークでは普通に起きる。

重要なのは、すべてのノードが常に同じ状態であることではなく、最終的に正しい状態に収束することである。

この考え方を結果整合性という。

ニュースアプリ→サーバーが正

ニュースアプリでは、基本的にサーバーやDBが正しい状態を持つ。

クライアント側のデータはキャッシュであり、古くなったらサーバーから再取得すればよい。

3. BLEでやってはいけない設計

toggle操作は危険

照明をON/OFFする場合、直感的には次のような命令を作りたくなる。

toggle light

しかし、BLEではこれは危険。

本当はONにしたかっただけなのに、同じメッセージが2回届いたことでOFFに戻ってしまう。

BLEでは同じメッセージが複数回届くことがあるため、toggleのような操作は避けるべきである。

4. BLEで安全な設計

set操作にする

安全なのは、状態を反転させるのではなく、明示的に状態を指定すること。

set light = ON

set ON は何回届いても結果が同じ。

このように、同じ操作を何回実行しても結果が変わらない性質を冪等性という。

5. Swiftで実装してみる

ここでは、BLEで照明ON/OFF命令を受け取る側の処理をSwiftで書く。

ポイントは以下の3つ。

messageId で重複排除する
version で古い命令を無視する
toggle ではなく set ON / set OFF にする

import Foundation

struct LightStateMessage: Codable, Equatable {
    let lightId: String
    let isOn: Bool
    let version: Int
    let messageId: UUID
}
フィールド 意味
lightId 対象の照明ID
isOn trueならON、falseならOFF
version 状態の新しさ
messageId メッセージの一意なID

受信側

import Foundation

final class LightController {
    private let lightId: String

    private var currentVersion: Int = 0
    private var isOn: Bool = false
    private var processedMessageIds: Set<UUID> = []

    init(lightId: String) {
        self.lightId = lightId
    }

    func receive(_ message: LightStateMessage) {
        guard message.lightId == lightId else {
            return
        }

        // 1. 重複メッセージを無視する
        guard !processedMessageIds.contains(message.messageId) else {
            print("Duplicate message ignored:", message.messageId)
            return
        }
        processedMessageIds.insert(message.messageId)

        // 2. 古いバージョンを無視する
        guard message.version >= currentVersion else {
            print("Old message ignored. message version:", message.version)
            return
        }

        // 3. 状態を上書きする
        currentVersion = message.version
        isOn = message.isOn

        applyToHardware()
    }

    private func applyToHardware() {
        if isOn {
            print("Light \(lightId): ON")
        } else {
            print("Light \(lightId): OFF")
        }
    }
}

この実装では、同じメッセージが複数回来ても1回しか処理しない。

また、古いバージョンの命令が後から届いても、現在の状態を巻き戻さない。

送信側

import Foundation

func createLightMessage(
    lightId: String,
    isOn: Bool,
    version: Int
) -> LightStateMessage {
    return LightStateMessage(
        lightId: lightId,
        isOn: isOn,
        version: version,
        messageId: UUID()
    )
}

送信例

let controller = LightController(lightId: "light-1")

let message1 = createLightMessage(
    lightId: "light-1",
    isOn: true,
    version: 1
)

controller.receive(message1)
controller.receive(message1)

出力

Light light-1: ON
Duplicate message ignored: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX

6. 古い命令が後から届くケース

BLEでは、古い命令が後から届くことがある。

例えば、以下の順番で操作したとする。

version 1: set ON
version 2: set OFF

しかし、通信の遅延によって、受信順が逆になる可能性がある。

let controller = LightController(lightId: "light-1")

let oldMessage = createLightMessage(
    lightId: "light-1",
    isOn: true,
    version: 1
)

let newMessage = createLightMessage(
    lightId: "light-1",
    isOn: false,
    version: 2
)

controller.receive(newMessage)
controller.receive(oldMessage)

出力例

Light light-1: OFF
Old message ignored. message version: 1

7. ニュースアプリ側の実装と比較

ニュースアプリでは、サーバーにリクエストしてレスポンスを受け取る設計が基本になる。

例えば記事一覧取得はこうなる。

import Foundation

struct Article: Decodable {
    let id: String
    let title: String
}

final class NewsAPIClient {
    func fetchArticles() async throws -> [Article] {
        let url = URL(string: "https://example.com/articles")!
        let (data, _) = try await URLSession.shared.data(from: url)
        return try JSONDecoder().decode([Article].self, from: data)
    }
}

ニュースアプリでは、正しい状態はサーバー側にある。

クライアントが古い情報を持っていても、再取得すればよい。

Client → Server → DB

一方、BLEでは「サーバーに聞けば正しい状態が返る」という前提がない。
Node A: ON Node B: OFF Node C: 不明
そのため、BLEでは受信側が壊れないように設計する必要がある。

8. 比較まとめ

観点 BLEネットワーク ニュースアプリ
構造 分散型 中央集権型
正しい状態 各ノードが部分的に持つ サーバー/DBが持つ
通信 不安定 比較的安定
順序 保証されない 基本的に制御しやすい
重複 起きる前提 通常は少ない
設計方針 壊れても成立させる 正しいフローを作る
重要概念 冪等性、結果整合性、重複排除 API、DB、トランザクション

9. 本質的な違い

BLEでは、次のようなことが普通に起きる。

通信が届かない
同じ命令が何度も届く
古い命令が後から届く
ノードごとに状態がズレる

そのため、設計の中心は「通信を完璧にすること」ではない。
重要なのは、通信が壊れても、状態が壊れないことである。

まとめ

照明ON/OFFで考えると、BLEとニュースアプリの違いはかなり分かりやすい。

ニュースアプリでは、サーバーに聞けば正しい状態が分かるという前提で作れる。

一方BLEでは、最後に届いたデータが最新とは限らないという前提で作る必要がある。

そのためBLEでは、

toggleではなくsetを使う
messageIdで重複排除する
versionで古い命令を無視する
冪等に処理する

という設計が重要になる。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?