Creating a MQTT library

182 views
Skip to first unread message

yba...@gmail.com

unread,
Sep 16, 2021, 5:17:14 PM9/16/21
to golang-nuts
Hello everyone,

I am trying to design a new library to encode/decode MQTT messages. I began the work, and now I must decide how to design the TCP processing of my library, and I am facing two choices:
- Design in a non-blocking tcp way (that is let the user use select()/poll()/epoll() sycall).
- Or design it with blocking tcp io inside go routines, which would return 2 channels (read and write). Then let the user do a channel-select in its main go routine.

I fail to see which one is superior to the other.
Moreover: one is idiomatic go, but one the other hand I like the idea to provide a thin wrapper and let the developper user of the library to choose which way he handles his problematic.

What would you do?

Regards
Yves

Robert Engels

unread,
Sep 16, 2021, 11:23:24 PM9/16/21
to yba...@gmail.com, golang-nuts
The advantage of Go is to be able to use millions of blocking Go routines. 

Synchronous programming is much easier than async - especially for streaming protocols. 

On Sep 16, 2021, at 4:17 PM, yba...@gmail.com <yba...@gmail.com> wrote:

Hello everyone,
--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/e900640f-edaa-4f6a-9d8e-7c751841e498n%40googlegroups.com.

Axel Wagner

unread,
Sep 17, 2021, 2:04:47 AM9/17/21
to yba...@gmail.com, golang-nuts
On Thu, Sep 16, 2021 at 11:17 PM yba...@gmail.com <yba...@gmail.com> wrote:
- Design in a non-blocking tcp way (that is let the user use select()/poll()/epoll() sycall).
- Or design it with blocking tcp io inside go routines, which would return 2 channels (read and write). Then let the user do a channel-select in its main go routine.

Neither. Return a blocking iterator, something like

type Iterator struct {
    // Next blocks until a new message is available or the stream ends and returns if a new message is available.
    Next() bool
    // Message returns the current message. Must only be called after Next returns true.
    Message() []byte
    // Error returns the error reading the next message, if any.
    Error() err
}

and let the user use their own channels/goroutines, if they need it.

That is what I would consider idiomatic Go. I would not consider it idiomatic to return/accept channels as part of your API anymore.
 

I fail to see which one is superior to the other.
Moreover: one is idiomatic go, but one the other hand I like the idea to provide a thin wrapper and let the developper user of the library to choose which way he handles his problematic.

What would you do?

Regards
Yves

--

ben...@gmail.com

unread,
Sep 19, 2021, 9:46:06 PM9/19/21
to golang-nuts

Neither. Return a blocking iterator, something like

type Iterator struct {
    // Next blocks until a new message is available or the stream ends and returns if a new message is available.
    Next() bool
    // Message returns the current message. Must only be called after Next returns true.
    Message() []byte
    // Error returns the error reading the next message, if any.
    Error() err
}

and let the user use their own channels/goroutines, if they need it.

That is what I would consider idiomatic Go. I would not consider it idiomatic to return/accept channels as part of your API anymore.

I definitely agree with Axel on the blocking / non-blocking thing. Digressing from the main point, but it may be more idiomatic to just return a concrete type, like *mqtt.Reader or *mqtt.Connection (if your package name is "mqtt"). The concrete type will have the iterator methods, plus any others for MQTT-specific stuff. For example:

reader, err := mqtt.Dial("url") // reader is type *mqtt.Reader
if err != nil { ... }
for reader.Next() {
    msg, err := reader.Message()
    if err != nil { ... }
}
if reader.Err() != nil { ... }

The reason is that then it's easy for callers to call MQTT-specific methods on the concrete type as well. In parts of the program that *take* a message reader, however, you'll probably use a locally-defined interface with just the methods you need. If the mqtt package defines the interface, it'll invariably include more methods than most people need.

Other considerations:

* Perhaps call the error method just Err(). That's common, for example, bufio.Scanner.Err.
* Maybe make mqtt.Reader.Message() return an mqtt.Message struct, which has Bytes() and Text() methods, as well as other MQTT message-specific stuff (topic, message ID, etc).

-Ben

jfcg...@gmail.com

unread,
Sep 22, 2021, 8:54:42 AM9/22/21
to golang-nuts
Reply all
Reply to author
Forward
0 new messages