# 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
*ByIdmethods 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
*ByIDoperations take different amounts of time.
# Write the flow
Now let's actually make requests to this interface.
Create a
main.goand put a//go:build cffcomment at the top it. This is necessary for cff to be able to process this file.//go:build cff package mainElsewhere in the file, instantiate the fake client to send requests to.
var uber UberAPI = new(fakeUberClient)Declare a
main()and set up acontext.Contextwith 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.Flowwith 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.Paramscall 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.Flowdoes 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
Responsetype.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 *[..].ResponseIn cff, the results of a task must be consumed by another task or by a
cff.Resultsdirective which extracts values from a flow. Let's fix this.Declare a new
*Responsevariable above thecff.Flowcall, and usecff.Resultsto 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.Resultsin thecff.Flowcall 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 generateagain. You should see a message similar to the following.Processed 3 files with 0 errorsRun 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.
DriverByIDandRiderByIDare independent of each other so as soon as the trip information is available, cff runs them both concurrently.- When the rider information is available,
DriverByIDis still running, so cff runsLocationByIDconcurrently. - 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)