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で古い命令を無視する
冪等に処理する
という設計が重要になる。