bitfield / uptimerobot

Client library for UptimeRobot v2 API
MIT License
56 stars 11 forks source link

Pagination for getMonitors #11

Closed alandotcom closed 5 years ago

alandotcom commented 5 years ago

This adds pagination for getting monitors. It also returns the monitors uptime status based on the reported status from uptimerobot.

It also updates the CLI command to stream the responses using GetMonitorsChan.

alandotcom commented 5 years ago

Partially fixes #1

bitfield commented 5 years ago

Hey @lumberj! Awesome PR, thank you! I really appreciate it.

Getting the monitor status is a great improvement, and so is supporting pagination.

I'm interested in the channel approach to listing the monitors. What exactly is happening concurrently here? I mean, we're fetching the lists of monitors, and printing them out, but are we doing anything else at the same time? If not, why does it use a goroutine? If we want to print out monitors as we read them, rather than waiting until we've read all pages, would it be better to pass GetMonitors an io.Writer to write to?

alandotcom commented 5 years ago

That’s a good point. In the case where were printing things out, we could just use an io Writer and read from that.

In an another case where we might want to take the monitor results and do some blocking operation on them (e.g., send that uptime data to some other service), having a channel where we could read those results with N workers is useful.

On Sun, Jul 21, 2019 at 3:46 AM John Arundel notifications@github.com wrote:

Hey @lumberj https://github.com/lumberj! Awesome PR, thank you! I really appreciate it.

Getting the monitor status is a great improvement, and so is supporting pagination.

I'm interested in the channel approach to listing the monitors. What exactly is happening concurrently here? I mean, we're fetching the lists of monitors, and printing them out, but are we doing anything else at the same time? If not, why does it use a goroutine? If we want to print out monitors as we read them, rather than waiting until we've read all pages, would it be better to pass GetMonitors an io.Writer to write to?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/bitfield/uptimerobot/pull/11?email_source=notifications&email_token=AAN2NZCZTB2TLSYCBWDS6UTQAQ47RA5CNFSM4IFQTUV2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD2OAX3Q#issuecomment-513543150, or mute the thread https://github.com/notifications/unsubscribe-auth/AAN2NZEK623DT3ZDS66VKIDQAQ47RANCNFSM4IFQTUVQ .

alandotcom commented 5 years ago

Here's a contrived example of what I might want to do:

import (
    "fmt"
    "github.com/bitfield/uptimerobot/pkg"
    "log"
    "sync"
)

func doWork(monitors <-chan uptimerobot.Monitor, wg *sync.WaitGroup) {
    defer wg.Done()
        // work
}

func main() {
    client := uptimerobot.New("XXXX")
    monitorResponse := client.GetMonitorsChan()
    done := make(chan struct{})

    go func() {
        var wg sync.WaitGroup

        // Run 50 goroutines to read from <-Monitor and do some work
        for i := 0; i < 50; i++ {
            wg.Add(1)
            go doWork(monitorResponse.Monitors, &wg)
        }
        wg.Wait()
        close(done)
    }()

    // Block until we get an error from the monitorResponse.Error chan or work is done
    select {
    case err := <-monitorResponse.Error:
        log.Fatal(err)
    case <-done:
        return
    }
}
bitfield commented 5 years ago

I love goroutines and channels as much as anyone (more, probably), but I'm wary nowadays of making them part of my APIs. The sense I get is that concurrency should either be on the library side, or on the user side, but not straddling the API boundary, if you know what I mean. So we might make the monitor fetching concurrent, but return a single, complete list to the user in one go, or we might provide functions the user can call concurrently if they want to (for example NextPage() or something like that. The Google Cloud API does this.)

How would it be if we keep the new GetMonitors which implements, but hides, pagination, and hold GetMonitorsChan in abeyance for the time being while we think about how to fit it into the overall design scheme?

alandotcom commented 5 years ago

Thank you for the well thought out response. I’m still new to Go development.

I’ll update the PR with your suggestions.

I found this, https://inconshreveable.com/07-08-2014/principles-of-designing-go-apis-with-channels/, helpful to understand why so few APIs send/receive channels (and the many problems that might result when that does happen).

On Mon, Jul 22, 2019 at 4:05 AM John Arundel notifications@github.com wrote:

I love goroutines and channels as much as anyone (more, probably), but I'm wary nowadays of making them part of my APIs. The sense I get is that concurrency should either be on the library side, or on the user side, but not straddling the API boundary, if you know what I mean. So we might make the monitor fetching concurrent, but return a single, complete list to the user in one go, or we might provide functions the user can call concurrently if they want to (for example NextPage() or something like that. The Google Cloud API does this.)

How would it be if we keep the new GetMonitors which implements, but hides, pagination, and hold GetMonitorsChan in abeyance for the time being while we think about how to fit it into the overall design scheme?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/bitfield/uptimerobot/pull/11?email_source=notifications&email_token=AAN2NZF33DRFSJZK3ENYZJ3QAWH7FA5CNFSM4IFQTUV2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD2PSLTA#issuecomment-513746380, or mute the thread https://github.com/notifications/unsubscribe-auth/AAN2NZCSA4RMVBSGRDF5OK3QAWH7FANCNFSM4IFQTUVQ .

alandotcom commented 5 years ago

So we might make the monitor fetching concurrent, but return a single, complete list to the user in one go, or we might provide functions the user can call concurrently if they want to (for example NextPage() or something like that. The Google Cloud API does this.)

Do you prefer leaving the current function as is (return the first page only), or, paginate and collect all the results then return the single set of results?

Thank you for pointing me to the Google Cloud API. They do have a section for implementing iterators,

https://github.com/googleapis/google-cloud-go/wiki/Iterator-Guidelines

I also found this a good read on three possible patterns, https://blog.kowalczyk.info/article/1Bkr/3-ways-to-iterate-in-go.html

They also recommend the iterator approach, as it provides the best API, and is more common.