Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement service bindings + local mode dev #1040

Closed
threepointone opened this issue May 17, 2022 · 6 comments · Fixed by #1503 or #1537
Closed

feat: implement service bindings + local mode dev #1040

threepointone opened this issue May 17, 2022 · 6 comments · Fixed by #1503 or #1537
Assignees
Labels
enhancement New feature or request

Comments

@threepointone
Copy link
Contributor

Service bindings ([services]) works with remote mode in wrangler dev, we should figure out how to do the same for local mode. This is part of the broader picture for multi worker, but filing this as a followup for #906 anyway.

@threepointone threepointone added the enhancement New feature or request label May 17, 2022
@threepointone threepointone moved this to Untriaged in workers-sdk May 17, 2022
@threepointone threepointone moved this from Untriaged to Backlog in workers-sdk May 17, 2022
@chrstntdd
Copy link

Instead of creating a new issue, I figured it would be better to have this discussion here.

Wanted to share my use of service bindings so far to find out of I've got a reliable approach.

For context, I have an app that runs on two workers. Worker A renders a web application and calls to Worker B (JSON API) for data using a service binding. In local development, I prefer running everything with --local for simplicity. I begin by running worker B in its own background terminal window. Worker A makes use of a conditional API_ENDPOINT string that switches based on an environment variable that is read by the custom build scripts.

From worker A, before I render the web app to an HTML string, I have to provide a fetch facade to a global Provider so that worker side network calls are handled correctly. Within the browser entry point of this app, I use a similar pattern, but provide the standard fetch global.

Here's about what it looks like in code:

// render-browser.tsx
import { hydrate } from "solid-js/web"

import { App } from "./app"
import { FetchCtx } from "./global-ctx"


hydrate(
  () => (
    <FetchCtx.Provider value={fetch}>
      <App />
    </FetchCtx.Provider>
  ),
  document,
)
// render-worker.tsx
import { renderToStringAsync } from "solid-js/web"

import { App } from "./app"
import { FetchCtx } from "./global-ctx"

export async function renderAppToString(props): Promise<string> {
  return renderToStringAsync(() => (
    <FetchCtx.Provider value={props.fetch}>
      <App />
    </FetchCtx.Provider>
  ))
}
// worker.tsx
import { getAssetFromKV } from "@cloudflare/kv-asset-handler"
import { $fetch } from "ohmyfetch"

import { renderAppToString } from "./render-worker"

export default {
  async fetch(request: Request, env: any, ctx: any): Promise<Response> {
    const url = new URL(request.url)

    if (/\.\w+/.test(url.pathname)) {
      return getAssetFromKV(/**/)
    }

    let appAsStr = await renderAppToString({
      fetch: async (req, opts) => {
        // Use service bound fetch if exists, otherwise use builtin
        let $$ = $fetch.create({
          fetch: env.api?.fetch ? (...x) => env.api.fetch(...x) : fetch,
        })

        return $$(req, opts)
      },
    })

    return new Response("<!doctype html>" + appAsStr, {
      headers: { "Content-Type": "text/html;charset=UTF-8" },
    })
  },
}

So far this works, but I have some thoughts and questions:

  • Is this the suggested way of interacting with a worker that can be messaged locally in dev and with a service binding in prod?
  • The dual nature of this approach has meant I have a second wrangler.dev.toml which uses dev-variant scripts in build.command to emit the correct API_ENDPOINT. Ideally I could have just one config and maybe a way to define an environment mappings to the corresponding command?
  • Is this a sustainable approach once more than 1 service binding is used?

All around, love what's happening here and what this enables for folks. Cheers!

@petebacondarwin
Copy link
Contributor

This is really cool @chrstntdd. Thanks for the write up. I think what you are doing is pretty much the best you can manage right now. I like the way you use the FetchCtx to allow browser and Worker rendering.
I wasn't quite sure where the API_ENDPOINT identifier comes in. I don't see it in your code. I guess it is in your wrangler.tomls? It would be interesting to see that part of the set up too.

@chrstntdd
Copy link

Ah yeah, apologies for leaving that bit out. It's not too involved at the moment, but it goes like this:

// env.ts

// `ENV` is injected by the build scripts with process.env.NODE_ENV or equivalent.
export const API_ENDPOINT = ENV === "production" ? "https://api.<WORKER>.com" : "http://localhost:3001"

And the usage within the application like so:

import { createResource } from "solid-js"

import { API_ENDPOINT } from "./config/env"
import { useFetch } from "./global-ctx"

export function App() {
  let fetch = useFetch()
  let [x] = createResource(`${API_ENDPOINT}/get-some-data`, async (key) => {
    let content = await fetch(key)
    return content
  })

  return (
    <section>
      <div>SSR data from worker:</div>
      <pre>{JSON.stringify(x(), null, 2)}</pre>
    </section>
  )
}

@threepointone
Copy link
Contributor Author

Hey @chrstntdd, would you like to try the solution we just landed in wrangler@beta? You simply have to run wrangler dev --local on both workers in different terminal tabs, and they should automatically bind to each other.

@chrstntdd
Copy link

Hey @threepointone!
I upgraded to the beta release and it runs smoothly! Was able to cut down the fetch facade that gets passed to the client to only:

// ...
      fetch: async (req, opts) => {
        let $$ = $fetch.create({
          fetch: (...x) => env.api.fetch(...x),
        })

        return $$(req, opts)
      },

Many thanks for the fixture you put together here 😄

@Qxxxx
Copy link

Qxxxx commented Apr 5, 2023

Hi @threepointone, has this feature already merged into wrangler 2.14.0?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
4 participants