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

Flow 101 fix to cope with the last Flow API. #147

Merged
merged 6 commits into from
Apr 25, 2019
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 93 additions & 60 deletions Flow101/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ Due to ongoing changes to Fn core, parts of this tutorial
may not function as described. Check back for updates.
```

This tutorial is based on [Matthew Gilliard's "Flow 101" blog post](https://mjg123.github.io/2017/10/10/FnProject-Flow-101.html).
This tutorial is based on [Matthew Gilliard's "Flow 101" blog post](https://mjg123.github.io/2017/10/10/FnProject-Flow-101.html).

:point_up: Please be aware that the Fn Flow API has evolved since Matthew wrote his "Flow 101" post.

[Fn Project](http://fnproject.io/) was released in October 2017. [Chad Arimura](https://twitter.com/chadarimura/) explained the motivation and structure of the project in a good amount of detail in his post ["8 Reasons why we built the Fn Project"](https://medium.com/fnproject/8-reasons-why-we-built-the-fn-project-bcfe45c5ae63), with one of the major components being **Fn Flow**. Flow allows developers to build high-level workflows of functions with some notable features:

Expand All @@ -31,23 +33,25 @@ A simple Flow function looks like this:
public String handleRequest(int x) {

Flow flow = Flows.currentFlow();
String funcToInvoke = "..."; // functionId of the function to invoke

return flow.completedValue(x)
.thenApply(i -> i+1)
.thenCompose( s -> Flow.invokeFunction("./isPrime", s) )
.thenCompose( s -> Flow.invokeFunction(funcToInvoke, ... )
.get();
}

```
:point_up: For illustration purpose, the above code is a simplification of the Fn Flow API and hence it won't compile as-is.

If you've used a promises-style API before then this will be familiar. The closest analogue in core Java is the [CompletionStage API](http://download.java.net/java/jdk9/docs/api/java/util/concurrent/CompletionStage.html) which was even called [`Promise`](http://cs.oswego.edu/pipermail/concurrency-interest/2012-December/010423.html) in a pre-release draft.
If you've used a promises-style API before then this will be familiar. The closest analogue in core Java is the [CompletionStage API](http://download.java.net/java/jdk9/docs/api/java/util/concurrent/CompletionStage.html).

Anyway it's easy to tell the stages of what's going to happen:

- Start with a value provided by the user
- Apply some transformation `i -> i+1`
- Pass that to an external function called `./isPrime`
- Then return get the result and return it
- then apply some transformation `i -> i+1`
- then pass that to an external function
- finally wait for the return and return it.

Internally the `Flow` class submits each stage in this workflow to the Flow Server. You'll meet it soon. The Flow Server will then orchestrate each stage as an individual call to Fn. Flow Server is responsible for working out which stages are ready to be called, calling them, handling the results and triggering any following stages until you reach the point where there's no more work to do.

Expand All @@ -64,16 +68,16 @@ Currently FnProject is available to download, to experiment with, and to run on
Install the **`fn`** CLI tool:

![user input](../images/userinput.png)
>```sh
>curl -LSs https://raw.githubusercontent.com/fnproject/cli/master/install | sh
>```
```sh
curl -LSs https://raw.githubusercontent.com/fnproject/cli/master/install | sh
```

Then start the **Fn server**:

![user input](../images/userinput.png)
>```sh
>fn start
>```
```sh
fn start
```

The output looks something like the following. The version number below is old. You should see the latest version number in your case.

Expand All @@ -85,7 +89,7 @@ time="2017-10-11T13:12:44Z" level=info msg="Serving Functions API on address `:8
/ /_ / __ \
/ __/ / / / /
/_/ /_/ /_/
v0.3.119
v0.3.629
```

The **Flow Server** needs to know how to call the Fn server, so ask Docker which IP address to use.
Expand All @@ -103,14 +107,14 @@ FNSERVER_IP=$(docker inspect --type container -f '{{.NetworkSettings.I
Start the **Flow Server**:

![user input](../images/userinput.png)
>```sh
>docker run --rm -d \
> -p 8081:8081 \
> -e API_URL="http://$FNSERVER_IP:8080/r" \
> -e no_proxy=$FNSERVER_IP \
> --name flowserver \
> fnproject/flow:latest
>```
```sh
docker run --rm -d \
-p 8081:8081 \
-e API_URL="http://$FNSERVER_IP:8080/invoke" \
-e no_proxy=$FNSERVER_IP \
--name flowserver \
fnproject/flow:latest
```

Then start the Flow **UI**:

Expand All @@ -126,14 +130,14 @@ FLOWSERVER_IP=$(docker inspect --type container -f '{{.NetworkSettings


![user input](../images/userinput.png)
>```sh
>docker run --rm -d \
> -p 3002:3000 \
> --name flowui \
> -e API_URL=http://$FNSERVER_IP:8080 \
> -e COMPLETER_BASE_URL=http://$FLOWSERVER_IP:8081 \
> fnproject/flow:ui
>```
```sh
docker run --rm -d \
-p 3002:3000 \
--name flowui \
-e API_URL=http://$FNSERVER_IP:8080 \
-e COMPLETER_BASE_URL=http://$FLOWSERVER_IP:8081 \
fnproject/flow:ui
```


Now, everything's set so lets crack on!
Expand All @@ -144,49 +148,69 @@ Now, everything's set so lets crack on!
Create a new function:

![user input](../images/userinput.png)
>```sh
>fn init --runtime=java simple-flow
>```
```sh
fn init --runtime=java simple-flow
```

Change directory:

![user input](../images/userinput.png)
>```sh
>cd simple-flow
>```
```sh
cd simple-flow
```

Flow has a comprehensive test framework, but lets concentrate on playing with the code for the time being:

![user input](../images/userinput.png)
>```sh
> rm -rf src/test ## yolo
>```
```sh
rm -rf src/test ## yolo
```

Make peace with yourself after that, then let's get the code in shape.

First, update the `pom.xml` to use Fn Flow.

```xml
<dependencies>
...
<dependency>
<groupId>com.fnproject.fn</groupId>
<artifactId>flow-runtime</artifactId>
<version>LATEST</version>
</dependency>
...
</dependencies>
```


![user input](../images/userinput.png) Change `HelloFunction.java` to look like this:

```java
package com.example.fn;

import com.fnproject.fn.api.FnFeature;
import com.fnproject.fn.api.flow.Flow;
import com.fnproject.fn.api.flow.Flows;
import com.fnproject.fn.runtime.flow.FlowFeature;

@FnFeature(FlowFeature.class)
public class HelloFunction {

public String handleRequest(int x) {

Flow fl = Flows.currentFlow();
Flow fl = Flows.currentFlow();

return fl.completedValue(x)
.thenApply( i -> i*2 )
.thenApply( i -> "Your number is " + i )
.get();
return fl.completedValue(x)
.thenApply( i -> i*2 )
.thenApply( i -> "Your number is " + i )
.get();
}
}
```

Then deploy this to an app which we call `flow101` on the local Fn server.
:point_up: Double check that your function is importing `com.fnproject.fn.api.flow.Flow` and not `java.util.concurrent.Flow`!

Then deploy this function to an app which we call `flow101` on the local Fn server.

![user input](../images/userinput.png)
>```sh
Expand All @@ -200,11 +224,11 @@ Then configure the function to talk to the Flow Server.
>fn config app flow101 COMPLETER_BASE_URL "http://$FLOWSERVER_IP:8081"
>```

You can now invoke the function using `fn call`:
You can now invoke the function using `fn invoke flow101 simple-flow`:

![user input](../images/userinput.png)
>```sh
>echo 2 | fn call flow101 /simple-flow
>echo 2 | fn invoke flow101 simple-flow
>```

The output looks something like the following:
Expand All @@ -213,18 +237,7 @@ The output looks something like the following:
Your number is 4
```

Alternatively, you can now invoke the function using `curl`:

![user input](../images/userinput.png)
>```sh
>curl -d "2" http://localhost:8080/r/flow101/simple-flow
>```

The output looks something like the following:

```
Your number is 4
```
Alternatively, you can configure an HTTP trigger and invoke the function using `curl`.

## Exploring the Flow UI

Expand All @@ -234,13 +247,33 @@ Browsing to [http://localhost:3002](http://localhost:3002) you should see someth

Which is showing us 3 function invocations:

* The main flow function, in blue
* The main flow function at the top
* `.thenApply` for the code `i -> i*2`
* `.thenApply` for the code `i -> "Your number is " + i`

Click on any of these and see the detail for each one expanded at the bottom of the screen.

The blue function is shown as running for the whole time that the `thenApply` stages are. Why? Because we are calling `.get()` at the end, so this is synchronously waiting for the final result of the chain. Exercise: Try removing the `.get()` from the code (you'll need to return a different String, and don't forget to re-deploy). Now it will look like:
:point_up: You might want to zoom to see more details, see the buttons at the bottom of the UI.

Note that Fn Flow is using *functionId* to reference functions. To know the *functionId* of the main function, just use the following command: `fn inspect function flow101 simple-flow` or simply `fn i f flow101 simple-flow` ~if you're lazy~ to save some time.

```json
{
"annotations": {
"fnproject.io/fn/invokeEndpoint": "http://localhost:8080/invoke/01CXZF8FF8NG8G00GZJ000002R"
},
"app_id": "...",
"created_at": "...",
"id": "01CXZF8FF8NG8G00GZJ000002R",
...
}
```

The `id` should match the *functionId* of the main function that you see in the UI.

:point_up: Make sure to not confuse `id` which is the *functionId* with the *applicationId*, i.e. `app_id`.

The main function is shown as running for the whole time that the `thenApply` stages are. Why? Because we are calling `.get()` at the end, so this is synchronously waiting for the final result of the chain. Exercise: Try removing the `.get()` from the code (you'll need to return a different String, and don't forget to re-deploy). Now it will look like:

![flow-ui](images/simple-flow-ui-async.png)

Expand Down
Binary file modified Flow101/images/simple-flow-ui-async.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Flow101/images/simple-flow-ui.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.