Message
ControlBee에서 액터 간 통신에 사용되는 메시지 클래스입니다. 메시지는 불변(immutable)이며 발신자 참조, 메시지 이름 및 선택적 페이로드를 포함합니다.
네임스페이스: ControlBee.Models
개요
메시지는 액터 간의 주요 통신 메커니즘입니다. 각 메시지는 생성되면 불변이며 다음을 포함합니다:
- Sender - 메시지를 보내는 액터에 대한 참조
- Name - 메시지 타입의 문자열 식별자
- Payload - 메시지가 전달하는 선택적 데이터
- Id - 메시지 추적을 위한 고유 식별자
- RequestId - 이 메시지를 요청과 연결하는 선택적 식별자
생성자
기본 생성자
public Message(IActor sender, string name)
public Message(IActor sender, string name, object? payload)
매개변수:
sender— 이 메시지를 보내는 액터name— 메시지 이름/타입 식별자payload— 선택적 데이터 페이로드
사용 예시:
using Dict = System.Collections.Generic.Dictionary<string, object?>;
// 간단한 메시지
var msg1 = new Message(this, "Start");
// 간단한 데이터를 포함한 메시지
var msg2 = new Message(this, "SetSpeed", 100.0);
// Dict 페이로드를 포함한 메시지
var msg3 = new Message(this, "ProcessCell", new Dict
{
["Row"] = 5,
["Col"] = 3
});
요청-응답 생성자
public Message(Guid requestId, IActor sender, string name, object? payload)
public Message(Message requestMessage, IActor sender, string name, object? payload)
매개변수:
requestId— 원래 요청 메시지의 GUIDrequestMessage— 응답할 원래 요청 메시지sender— 이 응답을 보내는 액터name— 응답 메시지 이름payload— 선택적 응답 데이터
사용 예시:
// 요청에 응답
public override bool ProcessMessage(Message message)
{
if (message.Name == "GetStatus")
{
// 요청에 연결된 응답 전송
var response = new Message(message, this, "StatusResponse", currentStatus);
message.Sender.Send(response);
return true;
}
return false;
}
속성
Sender
public IActor Sender { get; }
이 메시지를 보낸 액터입니다. 발신지를 식별하고 응답을 보내는 데 사용됩니다.
사용 예시:
public override bool ProcessMessage(Message message)
{
if (message.Name == "QueryPosition")
{
// 발신자에게 응답 전송
message.Sender.Send(new Message(this, "PositionResponse", currentPosition));
return true;
}
return false;
}
Name
public string Name { get; }
메시지 타입 식별자입니다. 다양한 메시지 타입을 처리하기 위해 switch 문에서 사용됩니다.
사용 예시:
switch (message.Name)
{
case "Start":
HandleStart();
return true;
case "Stop":
HandleStop();
return true;
default:
return false;
}
Payload
public object? Payload { get; }
메시지 데이터 페이로드입니다. 간단한 값, 객체 또는 딕셔너리 등 모든 타입이 가능합니다.
사용 예시:
// 간단한 페이로드
if (message.Name == "SetSpeed" && message.Payload is double speed)
{
Motor.Speed = speed;
return true;
}
// 객체 페이로드
if (message.Name == "SetPosition" && message.Payload is Position3D pos)
{
MoveToPosition(pos);
return true;
}
DictPayload
public Dictionary<string, object?>? DictPayload { get; }
페이로드가 Dictionary인 경우 Dictionary로 반환하고, 그렇지 않으면 null을 반환하는 편의 속성입니다. 페이로드가 Dictionary<string, object?>일 때 자동으로 설정됩니다.
사용 예시:
if (message.Name == "LoadProject" && message.DictPayload != null)
{
var index = message.DictPayload.GetValueOrDefault("Index") as int?;
var filePath = message.DictPayload.GetValueOrDefault("ProjectFile") as string;
if (filePath != null)
{
LoadProject(index ?? 0, filePath);
}
return true;
}
Id
public Guid Id { get; }
이 메시지의 고유 식별자입니다. 메시지가 생성될 때 자동으로 생성됩니다.
사용 사례:
- 메시지 추적 및 로깅
- 메시지 흐름 디버깅
- 액터 간 메시지 상관관계
- 성능 모니터링
사용 예시:
Logger.Info($"Received message {message.Id}: {message.Name} from {message.ActorName}");
RequestId
public Guid RequestId { get; }
이 메시지를 요청 메시지와 연결하는 식별자입니다. 응답이 아닌 경우 Guid.Empty입니다.
사용 예시:
// 특정 요청에 대한 응답인지 확인
if (message.RequestId == pendingRequestId)
{
ProcessResponse(message);
pendingRequestId = Guid.Empty;
return true;
}
ActorName
public string ActorName { get; }
Sender.Name을 반환하는 편의 속성입니다. 로깅 및 디버깅에 유용합니다.
사용 예시:
Logger.Debug($"Processing {message.Name} from {message.ActorName}");
특수 메시지
Empty Message
public static readonly Message Empty
자리 표시자로 사용되는 싱글톤 빈 메시지입니다. 액터에게 전송되어서는 안 됩니다.
TimerMessage
타이머 간격이 구성된 액터에게 주기적으로 전송되는 특수 시스템 메시지입니다.
메시지 이름: TimerMessage.MessageName
구성:
액터 생성자에서 TimerMilliseconds 속성을 설정하여 주기적인 타이머 메시지를 활성화합니다:
public FlipperActor(ActorConfig config) : base(config)
{
TimerMilliseconds = 200; // 200ms마다 TimerMessage 수신
// ... 나머지 초기화
}
사용법:
타이머 메시지는 일반적으로 스캔 작업, 주기적인 상태 확인 또는 센서 폴링에 사용됩니다:
public override bool ProcessMessage(Message message)
{
switch (message.Name)
{
case TimerMessage.MessageName:
return Scan(); // 주기적인 작업
// ... 다른 메시지들
}
return false;
}
private bool Scan()
{
// 센서 확인, 상태 업데이트 등
if (SensorChanged())
{
UpdateStatus();
return true;
}
return false;
}
참고: TimerMilliseconds가 설정되지 않았거나 0으로 설정된 경우, 타이머 메시지가 전송되지 않습니다.
메시지 패턴
1. 간단한 명령
// 발신자
NextStage.Send(new Message(this, "Start"));
2. 데이터를 포함한 명령
// 발신자
Picker.Send(new Message(this, "SetSpeed", 150.0));
// 수신자
if (message.Name == "SetSpeed" && message.Payload is double speed)
{
Motor.Speed = speed;
return true;
}
3. Dict를 사용한 복잡한 데이터
// 발신자
Head.Send(new Message(this, "Pick", new Dict
{
["X"] = 100.5,
["Y"] = 200.3,
["Z"] = 50.0,
["Speed"] = 100.0
}));
// 수신자
if (message.Name == "Pick" && message.DictPayload != null)
{
var x = message.DictPayload.GetValueOrDefault("X") as double?;
var y = message.DictPayload.GetValueOrDefault("Y") as double?;
var z = message.DictPayload.GetValueOrDefault("Z") as double?;
var speed = message.DictPayload.GetValueOrDefault("Speed") as double? ?? 50.0;
if (x.HasValue && y.HasValue && z.HasValue)
{
PickAt(x.Value, y.Value, z.Value, speed);
}
return true;
}
4. 요청-응답 패턴
// 요청
var requestId = Guid.NewGuid();
var request = new Message(requestId, this, "GetStatus");
peer.Send(request);
pendingRequestId = requestId;
// 응답 (피어 액터에서)
if (message.Name == "GetStatus")
{
var response = new Message(message, this, "StatusResponse", GetCurrentStatus());
message.Sender.Send(response);
return true;
}
// 응답 처리 (원래 액터에서)
if (message.Name == "StatusResponse" && message.RequestId == pendingRequestId)
{
HandleStatusResponse(message.Payload);
pendingRequestId = Guid.Empty;
return true;
}
모범 사례
1. 설명적인 이름 사용
new Message(this, "StartTransfer"); // ✅ 명확한 의도
new Message(this, "Msg1"); // ❌ 불명확
2. 복잡한 데이터는 Dict 사용
// ✅ 좋음 - 유연하고 자체 문서화됨
new Message(this, "Configure", new Dict
{
["Speed"] = 100.0,
["Acceleration"] = 200.0,
["Deceleration"] = 150.0
});
// ❌ 나쁨 - 취약하고 불명확
new Message(this, "Configure", new object[] { 100.0, 200.0, 150.0 });
3. 페이로드 타입 확인
// ✅ 좋음 - 안전한 타입 확인
if (message.Payload is Position3D pos)
{
MoveToPosition(pos);
}
// ❌ 나쁨 - 예외를 발생시킬 수 있음
var pos = (Position3D)message.Payload!;
MoveToPosition(pos);
4. 쿼리에는 요청-응답 사용
// ✅ 좋음 - 명확한 요청-응답 패턴
var response = new Message(message, this, "Response", data);
message.Sender.Send(response);
// ❌ 나쁨 - 연결되지 않은 별도의 메시지
message.Sender.Send(new Message(this, "Response", data));
참고
- 메시지 전달 가이드 - 메시지 패턴 완벽 가이드
- IActor 인터페이스 - 메시지 전송
- IState 인터페이스 - 메시지 처리