#At a high level, how would you implement an asynch API in Go?

27 messages · Page 1 of 1 (latest)

copper agate
#

Interested in using gorilla/mux. I'm quite new to asynch APIs in general too. So say I just want to implement a simple http server in Go. I would like to just store the status of these asynch API job IDs in-memory for now. So when a request comes in, the point of an asynch API would be for a long running process which the caller of the API could poll periodically until it finishes right. So when the request comes in, is the idea to create a goroutine (I am new to goroutines) for the long running task, and then immediately return a job ID back to the caller so that they can poll for the status of the job? How would you do this?

cinder tundra
#

the only thing you commonly need is a locked map, which saves the job's info and status.
this can be easily done with standard package net/http, no need for third party frameworks.

first you need a map and a sync.Mutex to lock it (or just use sync.Map)

when a user invokes, create job status struct which also under a mutex (the implementation details is depends on what information you need), and then save the status in the map. You usually will have a context in the status for cancel the long running job later

in the goroutine, you just continues poll your job (also the context if you want to cancel it), and save the status to the struct you created. when the job is done, you can decides to remove the status struct from the map immediately, or keep it until a user gets the result.

copper agate
#

hm, so something like this would be missing this idea of polling for this job. I guess the job here is simple enough, but we might want a separate goroutine to periodically poll the status of the job locally to update the task store

func asyncHandler(w http.ResponseWriter, r *http.Request) {
    id := generateID()

    mu.Lock()
    taskStore[id] = TaskStatus{Status: "Processing"}
    mu.Unlock()

    go func(id string) {
        // Simulate a long-running task
        time.Sleep(5 * time.Second)

        mu.Lock()
        taskStore[id] = TaskStatus{Status: "Completed", Message: "Task completed successfully"}
        mu.Unlock()
    }(id)

    response := map[string]string{"id": id}
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(response)
}
#

oh, the routine that is executing the job should be constantly checking the store too right. in case i cancel it explicitly

#

and i like the idea of keeping the result around until a user polls a successful

copper agate
cinder tundra
copper agate
#

ah ok. The code base I am using is in gorilla/mux, so that's why I'm leaning on it.

#

but thanks, this is all very good to know

cinder tundra
#

I recommend don't assign taskStore[id] again, but change the TaskStatus directly by pointer (so you need a map to store *TaskStatus but not TaskStatus, and you also need a mutex in the TaskStatus)

copper agate
# cinder tundra yes this code looks good, unless you are doing something more complex

Yea, I can describe what I am working on. I work in database infrastructure (I'm a jr). We have basically a local container orchestrator that runs on every host that starts up the database containers. It's a simple http server with start, stop, status, and what it does it call start container on docker and so on. Right now, those APIs are synchronous, but sometimes the database can take much longer to start up, so it would make sense to make a set of async APIs

cinder tundra
#

If you are making dashboard, I'd recommend websocket for server side push

copper agate
#

There's no dashboard or anything.

#

oh does the taskstore also need a mutex?

#

i guess i can just simplify all this with sync.map

cinder tundra
copper agate
cinder tundra
#

It's not a big deal in your case tho

copper agate
#

so that's why we are looking into async

#

i see, that makes sense

cinder tundra
copper agate
#

yep, this makes sense

#

cool, thanks for helping!

cinder tundra
#

yw