iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 25
1
自我挑戰組

Let's Eat GO ! 實務開發雜談by Golang系列 第 25

Day25 .[心得與討論篇] struct 設計解析 - 以melody package (5)

雖然說struct是業務的主體,但實際上業務與業務之間的邊界,還是得靠method裡面的控管去處理,了解邊界處理的部分,才能夠了解package的作者,對於細節處理的想法,並且用來印證從struct看到的猜測。

這篇會來專門討論melody裡面業務邊界的處理。

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

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開始,跟大家分享裡面有趣的業務設計方式。


上一篇
Day24 .[心得與討論篇] struct 設計解析 - 以melody package (4)
下一篇
Day26 .[心得與討論篇] struct 設計解析 - 以melody package (6)
系列文
Let's Eat GO ! 實務開發雜談by Golang30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言