雖然說struct是業務的主體,但實際上業務與業務之間的邊界,還是得靠method裡面的控管去處理,了解邊界處理的部分,才能夠了解package的作者,對於細節處理的想法,並且用來印證從struct看到的猜測。
這篇會來專門討論melody裡面業務邊界的處理。
下面這個method非常關鍵,Session在這裡做初使化。
// HandleRequestWithKeys does the same as HandleRequest but populates session.Keys with keys.
func (m *Melody) HandleRequestWithKeys(w http.ResponseWriter, r *http.Request, keys map[string]interface{}) error {
if m.hub.closed() {
return errors.New("melody instance is closed")
}
conn, err := m.Upgrader.Upgrade(w, r, w.Header())
if err != nil {
return err
}
session := &Session{
Request: r,
Keys: keys,
conn: conn,
ooutput: make(chan *envelope, m.Config.MessageBufferSize),
melody: m,
open: true,
rwmutex: &sync.RWMutex{},
}
m.hub.register <- session
m.connectHandler(session)
go session.writePump()
session.readPump()
if !m.hub.closed() {
m.hub.unregister <- session
}
session.close()
m.disconnectHandler(session)
return nil
}
HandleRequestWithKeys 雖然是屬於melody的method,但乍看之下,你可能發現大多是處理關於session的操作。
這裡就是用『業務』的觀點,去思考業務和邏輯處理最困難的部分,所謂用『業務』去處理,不能像『資料』那麼單純,很簡單的不是A就是B,melody就處理melody的資料,session就處理session的資料。
不!不是這樣的,容我舉個例子。
假設你是個父親的角色,你自己會是個獨立的個體,你擁有個兒子,兒子也是個獨立的個體。
兒子的出生,兒子的教育,兒子的生活條件,兒子的林林總總會是你要關心和處裡的業務。
雖然這些事情圍繞的主角,是在你『兒子』這個個體上,但這些工作是屬於你『父親』這個角色該做的。
『兒子』當然也有ㄧ些他自己能處裡的事情,但就是專屬他的事情,『父親』不知道的業務處理。
『兒子』可能有些method是來處理跟『父親』的互動,但『父親』可能完全知道『兒子』怎麼想的對吧。
回歸正題,HandleRequestWithKeys告訴了我們,melody主要控制了session的開始與結束,並且hub是不是已經close,是緊要的重點,並且透過register和unregister通知道hub,讓hub接手後續的事情,雖然melody不知道hub想幹嘛,但是melody在這邊控制了session跟hub的註冊和註銷。
hub 擁有的 run() 更是golang的經典核心處理方式,有機會你可以在很多source code或者package裡面看到如此的設計。
func (h *hub) run() {
loop:
for {
select {
case s := <-h.register:
h.rwmutex.Lock()
h.sessions[s] = true
h.rwmutex.Unlock()
case s := <-h.unregister:
if _, ok := h.sessions[s]; ok {
h.rwmutex.Lock()
delete(h.sessions, s)
h.rwmutex.Unlock()
}
case m := <-h.broadcast:
h.rwmutex.RLock()
for s := range h.sessions {
if m.filter != nil {
if m.filter(s) {
s.writeMessage(m)
}
} else {
s.writeMessage(m)
}
}
h.rwmutex.RUnlock()
case m := <-h.exit:
h.rwmutex.Lock()
for s := range h.sessions {
s.writeMessage(m)
delete(h.sessions, s)
s.Close()
}
h.open = false
h.rwmutex.Unlock()
break loop
}
}
}
register、unregister、broadcast、exit 寫在同一個select,根據select的特性,同一時間只會處理某一種case,所以不會有同步處理的問題。
再進ㄧ步的說,不管是哪種業務,譬如說register,假設幾乎同時有上千個連線進來,都要跟hub辦理註冊的業務,那這些工作會因為是塞進了channel,自動有順序的排排站好,不會有後來卻先被處裡的狀況,並且一個處理完,才處理下一個,這樣的設計堪稱完美。
程式碼也很簡潔,你可以理解為被交辦了什麼業務工作,就去執行如此而已!
Session 的method裡面有些不錯的設計概念和做法,下一篇繼續從session開始,跟大家分享裡面有趣的業務設計方式。