反省を活かして、EntityにGetter・Setterを用意した話
2025-04-15
概要
せっかくなので、GetterやSetterをEntityに設けてみました。
package entity
type Message struct {
id string // ユーザーIDを期待する
msgType string
sdp string
candidate []string
targetID string
}
type WebsocketClient struct {
id string
sdp string
candidate []string
}
func NewMessage(id string, messageType string, sdp string, candidate []string, targetID string) *Message {
return &Message{
id: id,
msgType: messageType,
sdp: sdp,
candidate: candidate,
targetID: targetID,
}
}
func (m Message) GetID() string {
return m.id
}
func (m Message) GetType() string {
return m.msgType
}
func (m Message) GetSDP() string {
return m.sdp
}
func (m Message) GetCandidate() []string {
return m.candidate
}
func (m Message) GetTargetID() string {
return m.targetID
}
func (m *Message) SetID(id string) {
m.id = id
}
func (m *Message) SetType(messageType string) {
m.msgType = messageType
}
func (m *Message) SetSDP(sdp string) {
m.sdp = sdp
}
func (m *Message) SetCandidate(candidate []string) {
m.candidate = candidate
}
func (m *Message) SetTargetID(targetID string) {
m.targetID = targetID
}
func NewWebsocketClient(id, sdp string, candidate []string) *WebsocketClient {
return &WebsocketClient{
id: id,
sdp: sdp,
candidate: candidate,
}
}
func (w *WebsocketClient) GetID() string {
return w.id
}
func (w *WebsocketClient) GetSDP() string {
return w.sdp
}
func (w *WebsocketClient) GetCandidate() []string {
return w.candidate
}
func (w *WebsocketClient) SetID(id string) {
w.id = id
}
func (w *WebsocketClient) SetSDP(sdp string) {
w.sdp = sdp
}
func (w *WebsocketClient) SetCandidate(candidate []string) {
w.candidate = candidate
}
長ったらしいですね。
前回のEntityにゲッターとセッターが追加されただけです。
主に言及するのはこれによって変わったところや追加で必要になった実装などでしょうか。
"encoding/json"直接使えない問題
これが今回の実装の中で唯一学びがあったところでした。
ほかは、直接書き換えをしていたところをメソッドに置き換えていくだけ。
ここも、自由な操作をしていた頃と比べて遠回りになったり、不便なように感じたりしました。
が、これは今まで保守性に欠ける不正気味な操作をしてラクをしていたわけで。
これがあるべき姿な、はず。
題目にもなっているencoding/json直接使えない問題はinfrastructure/service_implで起こりました。
もともとは、websocket.Connとの結合を薄くするためにadapter的に導入しました。
後に引数を[]byteからentity.Messageに変更。
usecase層からentity.Messageを受け取ってinfrastructureの中でencoding/jsonを使って[]byteにしてました。
しかし、entity.Messageの直接参照が禁止されたことで、json.Marshalやjson.Unmarshalを受け取った値に直接使うことができなくなりました。
func (w *WebSocketConnectionImpl) ReadMessage() (int, entity.Message, error) {
messageType, messagebyte, err := w.conn.ReadMessageFunc()
if err != nil {
return 0, entity.Message{}, err
}
var message entity.Message
err = json.Unmarshal(messagebyte, &message)// <--ここが失敗!!!
if err != nil {
return 0, entity.Message{}, err
}
return messageType, message, nil
}
func (w *WebSocketConnectionImpl) WriteMessage(data entity.Message) error {
dataByte, err := json.Marshal(data)// <--ここが失敗!!!
if err != nil {
return err
}
return w.conn.WriteMessageFunc(websocket.TextMessage, dataByte)
}
ちなみに、wに含まれるconnは、テスト時にモックにできるにwebsocket.Connを隠すアダプターです。
DTOを使おう
この問題の解決法はズバリ、DTOを使うです。<-これの存在を今回の修正で知りました。お恥ずかしい
infra層にこんなものを置きます。
package dto
import "example.com/webrtc-practice/internal/domain/entity"
type WebsocketMessageDTO struct {
ID string `json:"id"`
Type string `json:"type"`
SDP string `json:"sdp"`
Candidate []string `json:"candidate"`
TargetID string `json:"target_id"`
}
func (w *WebsocketMessageDTO) ToEntity() *entity.Message {
return entity.NewMessage(
w.ID,
w.Type,
w.SDP,
w.Candidate,
w.TargetID,
)
}
func (w *WebsocketMessageDTO) FromEntity(msg *entity.Message) {
w.ID = msg.GetID()
w.Type = msg.GetType()
w.SDP = msg.GetSDP()
w.Candidate = msg.GetCandidate()
w.TargetID = msg.GetTargetID()
}
外部サービスに対するentiryのI/Oをお手伝いしてくれるオブジェクトです。
今回では、
websocket.Conn <--> adaptor.conn <--> service_impl.connection
↑ここ
ここのI/Oをお手伝いしてくれます。
適用してみましょう。
func (w *WebSocketConnectionImpl) ReadMessage() (int, entity.Message, error) {
messageType, messagebyte, err := w.conn.ReadMessageFunc()
if err != nil {
return 0, entity.Message{}, err
}
var message dto.WebsocketMessageDTO
err = json.Unmarshal(messagebyte, &message)
if err != nil {
return 0, entity.Message{}, err
}
messageEntity := message.ToEntity()
return messageType, *messageEntity, nil
}
func (w *WebSocketConnectionImpl) WriteMessage(data entity.Message) error {
dataDTO := dto.WebsocketMessageDTO{}
dataDTO.FromEntity(&data)
dataByte, err := json.Marshal(dataDTO)
if err != nil {
return err
}
return w.conn.WriteMessageFunc(websocket.TextMessage, dataByte)
}
これで、Entityの直接参照を防いだまま、うまくservice_implを動かせるようになりました〜