# Your first cff flow
# Set up a fake API
For the demo, we'll make a fake service to send requests to instead of using a real system.
Create a new file in your demo project named "api.go". Inside it, put the following interface.
type UberAPI interface { DriverByID(int) (*Driver, error) RiderByID(int) (*Rider, error) TripByID(int) (*Trip, error) LocationByID(int) (*Location, error) } type Driver struct { ID int Name string } type Location struct { ID int City string State string // ... } type Rider struct { ID int Name string HomeID int } type Trip struct { ID int DriverID int RiderID int }
This defines a pretty simple interface for a conceivable Uber API client:
- there are four resources: driver, rider, trip, location
- the
*ById
methods support retrieving them by ID - trips have a driver and a rider
- riders have a home location
This is obviously oversimplified, but it will suffice for the demo.
In the same file, or a new file, add the following fake implementation of this interface.
type fakeUberClient struct{} func (*fakeUberClient) DriverByID(id int) (*Driver, error) { time.Sleep(500 * time.Millisecond) return &Driver{ ID: id, Name: "Eleanor Nelson", }, nil } func (*fakeUberClient) LocationByID(id int) (*Location, error) { time.Sleep(200 * time.Millisecond) return &Location{ ID: id, City: "San Francisco", State: "California", }, nil } func (*fakeUberClient) RiderByID(id int) (*Rider, error) { time.Sleep(300 * time.Millisecond) return &Rider{ ID: id, Name: "Richard Dickson", }, nil } func (*fakeUberClient) TripByID(id int) (*Trip, error) { time.Sleep(150 * time.Millisecond) return &Trip{ ID: id, DriverID: 42, RiderID: 57, }, nil }
Note that the different
*ByID
operations take different amounts of time.
# Write the flow
Now let's actually make requests to this interface.
Create a
main.go
and put a//go:build cff
comment at the top it. This is necessary for cff to be able to process this file.//go:build cff package main
Elsewhere in the file, instantiate the fake client to send requests to.
var uber UberAPI = new(fakeUberClient)
Declare a
main()
and set up acontext.Context
with a one second timeout. We'll use this in our flow.func main() { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel()
Now start a new flow by calling
cff.Flow
with the context.func main() { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() err := cff.Flow(ctx,
Add a task to the flow to retrieve information about the trip.
err := cff.Flow(ctx, cff.Task(func(tripID int) (*Trip, error) { return uber.TripByID(tripID) }),
The task we just added expects the trip ID to be present in an integer. Let's give it that. Add a
cff.Params
call to provide the trip ID for this function.err := cff.Flow(ctx, cff.Params(12), cff.Task(func(tripID int) (*Trip, error) { return uber.TripByID(tripID) }),
The order in which this is passed to
cff.Flow
does not matter.With a trip object available, the flow can run other operations. Add two new tasks that, given a trip, will fetch the driver and the rider for that trip respectively.
err := cff.Flow(ctx, // ... cff.Task(func(trip *Trip) (*Driver, error) { return uber.DriverByID(trip.DriverID) }), cff.Task(func(trip *Trip) (*Rider, error) { return uber.RiderByID(trip.RiderID) }),
Again, the order in which these are provided does not matter.
One of the tasks we just added fetches information about a rider. Add another task that retrieves the rider's location.
err := cff.Flow(ctx, // ... cff.Task(func(rider *Rider) (*Location, error) { return uber.LocationByID(rider.HomeID) }),
We now have a few different tasks. Let's bring their results together. Declare a new
Response
type.type Response struct { Rider string Driver string HomeCity string }
Back in the flow, add a new task to build the Response from the outputs of the different tasks.
err := cff.Flow(ctx, // ... cff.Task(func(r *Rider, d *Driver, home *Location) *Response { return &Response{ Driver: d.Name, Rider: r.Name, HomeCity: home.City, } }),
Handle the error returned by
cff.Flow
. For the tutorial, we're just exiting the application. In a real application, you should handle this error more gracefully.cff.Task(func(r *Rider, d *Driver, home *Location) *Response { return &Response{ Driver: d.Name, Rider: r.Name, HomeCity: home.City, } }), ) if err != nil { log.Fatal(err) }
Run
go generate
. You'll see an error similar to the following:main.go:59:12: unused output type *[..].Response
In cff, the results of a task must be consumed by another task or by a
cff.Results
directive which extracts values from a flow. Let's fix this.Declare a new
*Response
variable above thecff.Flow
call, and usecff.Results
to pass a reference to it to the flow. This tells cff to fill that pointer with a value from the flow.var res *Response err := cff.Flow(ctx, cff.Params(12), cff.Results(&res), cff.Task(func(tripID int) (*Trip, error) {
As with previous cases--the position of
cff.Results
in thecff.Flow
call does not matter.Print the response after handling the error.
if err != nil { log.Fatal(err) } fmt.Println(res.Driver, "drove", res.Rider, "who lives in", res.HomeCity)
Finally, run
go generate
again. You should see a message similar to the following.Processed 3 files with 0 errors
Run the program with
go run .
:% go run . Eleanor Nelson drove Richard Dickson who lives in San Francisco
What did we just do?
After the setup in the first section, we build a cff flow with five tasks: TripByID, DriverByID, RiderByID, LocationByID, and the final task to build a Response.
These together form the following graph.
DriverByID
andRiderByID
are independent of each other so as soon as the trip information is available, cff runs them both concurrently.- When the rider information is available,
DriverByID
is still running, so cff runsLocationByID
concurrently. - When they're both finished,
cff finally brings the results together in
Response
.
Although the graph in this example is contrived, cff can and does handle graphs that are significantly more complex: more than it would be reasonable for a person to manually orchestrate concurrency for.
Next steps
- Explore the API Reference (opens new window)