From 9dddf2474e4cb9069cb2d2d5e8091291c8e19bb1 Mon Sep 17 00:00:00 2001 From: "eugene.tolmachev" Date: Thu, 24 Mar 2016 10:03:09 -0400 Subject: [PATCH] more docs and samples --- LICENSE.md | 2 +- README.md | 14 ++++----- docs/content/LICENSE.md | 2 +- docs/content/index.fsx | 62 +++++++++++++++++++++++-------------- docs/content/wordcount.fsx | 9 +++--- docs/files/img/logo.png | Bin 13291 -> 13773 bytes docs/tools/generate.fsx | 6 ++-- src/FsShelter/Task.fs | 4 ++- src/FsShelter/Topology.fs | 12 +++++++ 9 files changed, 71 insertions(+), 40 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index d955a86..86a8bee 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -2,7 +2,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 +http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, diff --git a/README.md b/README.md index f39b352..78e708b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -FsShelter [![Windows Build](https://ci.appveyor.com/api/projects/status/c0oom3oyr8qnrsc8?svg=true)](https://ci.appveyor.com/project/et1975/fsshelter) [![Mono/OSX build](https://travis-ci.org/Prolucid/FsShelter.svg?branch=master)](https://travis-ci.org/Prolucid/FsShelter) +FsShelter [![Windows Build](https://ci.appveyor.com/api/projects/status/c0oom3oyr8qnrsc8?svg=true)](https://ci.appveyor.com/project/et1975/fsshelter) [![Mono/OSX build](https://travis-ci.org/Prolucid/FsShelter.svg?branch=master)](https://travis-ci.org/Prolucid/FsShelter) [![NuGet version](https://badge.fury.io/nu/fsshelter.svg)](https://badge.fury.io/nu/fsshelter) ======= A library for defining and running Apache Storm topologies in F# using statically typed streams. @@ -10,7 +10,7 @@ See [docs][docs] for for an intro and an overview. Join the conversation: [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](TBD) -# Limitations +## Limitations * At the moment FsShelter doesn't support direct emits. * [STORM-1644](https://issues.apache.org/jira/browse/STORM-1644): Currently, when running on Windows, the process will run under cmd.exe incurring slight overhead. @@ -24,12 +24,12 @@ or on Linux/OSX: ./build.sh ``` -# Running the tests +## Running the tests Building from command line runs the unit tests. IDE: Install NUnit plugin for VS or MonoDevelop to see the unit-tests in Test Explorer and step through the code under debugger. -# Submitting the topology +## Submitting the topology Have a local [Storm](https://storm.apache.org/downloads.html) installed and running. ``` samples\WordCount\bin\Release\WordCount submit-local @@ -39,13 +39,13 @@ or, if running on Mono: mono samples/WordCount/bin/Release/WordCount.exe submit-local ``` -# Seeing the topology in action +## Seeing the topology in action Open [Storm UI](http://localhost:8080/) and see the Storm worker logs for runtime details. -# License +## License FsShelter is Apache 2.0 licensed and free to use and modify. -# Commercial support +## Commercial support Contact [Prolucid](http://prolucid.ca) for commercial support. [docs]:https://prolucid.github.io/FsShelter/ diff --git a/docs/content/LICENSE.md b/docs/content/LICENSE.md index d955a86..86a8bee 100644 --- a/docs/content/LICENSE.md +++ b/docs/content/LICENSE.md @@ -2,7 +2,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 +http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, diff --git a/docs/content/index.fsx b/docs/content/index.fsx index b179845..edee8e6 100644 --- a/docs/content/index.fsx +++ b/docs/content/index.fsx @@ -5,22 +5,34 @@ #r "FsShelter.dll" open System +open FsShelter (** -FsShelter -====================== - Overview ------- -FsShelter is a library for implementation of [Apache Storm](https://storm.apache.org/) components and definition of topologies in F# DSL. -The Management module also provides wrappers for Nimbus Thrift API, allowing to bundle and submit a topology for execution. -FsShelter is based on and a major rewrite of [FsStorm](https://github.com/FsStorm). It departs from FsStrom in significant ways and therefore has been split off into itsown project. +FsShelter is a library for implementation of [Apache Storm](https://storm.apache.org/) components and topologies in F#. +FsShelter is based on and a major rewrite of [FsStorm](https://github.com/FsStorm). It departs from FsStrom in significant ways and therefore has been split into itsown project. + +Overall, the librabry provides "batteries included" experience with wrappers for Nimbus API as well as support for packaging and exporting: + +- bundle and submit a topology for execution w/o needing JDK or Storm CLI +- include Storm-side serializer along +- kill a running topology +- generate a topology graph as part of your build + The topology and the components could be implemented in a single EXE project and are executed by Storm via its [multilang](https://storm.apache.org/documentation/Multilang-protocol.html) protocol as separate processes - one for each task/instance. -Corresponding [ProtoShell](https://github.com/prolucid/protoshell) and [ThriftShell](https://github.com/prolucid/thriftshell) libraries facilitate Protobuf and Thrift serialization, which improve throughput of FsShelter components as compared to standard JSON. +Corresponding [ProtoShell](https://github.com/prolucid/protoshell) and [ThriftShell](https://github.com/prolucid/thriftshell) libraries facilitate Protobuf and Thrift serialization, which improve throughput of FsShelter topologies as compared to standard JSON. +See samples to learn how to bundle the assemblies and a serializer for upload to Storm. + +Bring your own, if you need it: + +- command line parser +- logging +- custom serializer FsShelter topology schema ----------------------- -While Storm tuples are dynamically typed and to large extend the types are transparent to Storm itself, they are not types-less. -Mistakes and inconsistencies between declared outputs and tuple consumers could easily lead to errors detectable at run-time only and may be frustrating to test for, detect and fix. +While Storm tuples are dynamically typed and to a large extend the types are transparent to Storm itself, they are not types-less. +Mistakes and inconsistencies between declared outputs and tuple consumers could easily lead to errors detectable at run-time only and may be frustrating to test, detect and fix. FsShelter introduces concept of topology schema, defined as F# discriminated union: *) @@ -29,7 +41,8 @@ type BasicSchema = | Incremented of int (** -where every DU case becomes a distinct stream in the topology. +where every DU case becomes a distinct stream in the topology. The fields of each DU case will become tuple fields in Storm. + It is often handy to define a type that's shared across streams and FsShelter supports defining cases with records: *) @@ -54,17 +67,17 @@ FsShelter "flattens" the first immediate "layer" of the DU case so that all the FsShelter components ----------------------- - -FsShelter components are defined as simple functions: +Some of the flexibility of Storm has been hidden to provide simple developer experience for authoring event-driven solutions. +For exmple, FsShelter components are implemeted as simple functions: *) // numbers spout - produces messages let numbers source = async { return Some(Original(source())) } (** -the async body is expected to return an option if there's a tuple to emit. +The async body of a spout is expected to return an option if there's a tuple to emit or None if there's nothing to emit at this time. -Bolts can get a touple on any number of streams, and so we pattern match: +Bolts can get a tuple on any number of streams, and so we pattern match: *) // add 1 bolt - consumes and emits messages to Incremented stream @@ -77,8 +90,8 @@ let addOne (input, emit) = } (** -The bolt can also emit at any time, and can hold on to the passed emit function. -The can be as many arguments for the component functions as needed: +The bolt can also emit at any time, and we can hold on to the passed emit function (with caveates). +Also, there can be as many arguments for the component functions as needed, the specifics will be determined when the components are put together in a topology. *) // terminating bolt - consumes messages @@ -90,7 +103,11 @@ let logResult (info, input) = } (** -the specifics will be determined when the components are put together in a topology: + +Using F# DSL to define the topology +-------------------- + +Storm topology is a graph of spouts and bolts connected via streams. FsShelter provides an embedded DSL for defining the topologies, which allows for mix and match of native Java, external shell and FsShell components: *) // define our source dependency @@ -121,21 +138,20 @@ let sampleTopology = } (** -Storm will start (a copy of) the same EXE for every component instance in the topology and will instruct each instance with the task it supposed to execute. +Storm will start (a copy of) the same EXE for every component instance in the topology and will assign each instance a task it supposed to execute. -The compiled topology can be submitted using embedded Thrift client, see the examples for details. +The topology can be packaged with all its dependecies and submitted using embedded Nimbus client, see the examples for details. Exporting the topology graph in DOT format (GraphViz) using F# scripts ----------------------- +Once the number of components grows beyond handful it is often handy to be able to visualize them and FsStrom includes a simple way to export the topology into a graph: *) -#r "../../build/WordCount.exe" - -open FsShelter - sampleTopology |> DotGraph.writeToConsole (** +See the samples included for further details. + Samples & documentation ----------------------- diff --git a/docs/content/wordcount.fsx b/docs/content/wordcount.fsx index 8d22f67..a5db47b 100644 --- a/docs/content/wordcount.fsx +++ b/docs/content/wordcount.fsx @@ -3,6 +3,7 @@ #r "FsShelter.dll" open System +open FsShelter (** Defining the schema @@ -112,7 +113,7 @@ let increment = Using F# DSL to define the topology -------------------- -Storm topology is a graph of spouts and bolts connected via streams. FsShelter provides an embedded DSL for defining the topologies, which allows mix and match of native java, external shell and FsShell components: +Storm topology is a graph of spouts and bolts connected via streams that can be defined via `topology` computation expression: *) open FsShelter.DSL @@ -155,7 +156,7 @@ The lambda arguments for the "run" methods privde the opportunity to carry out c * emit is the function to emit another tuple "log" and "cfg" are fixed once (curried) and as demonstrated in logBolt mkArgs lambda, one time-initialization can be carried out by inserting arbitrary code before "tuple" and "emit" arguments. -This initialization will not be triggered unless the task execution is actually requsted by Storm for this specific instance of the process. +This initialization will not be triggered unless the task execution is actually requested by Storm for this specific instance of the process. Exporting the topology graph @@ -166,13 +167,13 @@ FsShelter includes a completely customizable GraphViz (dot) export functionality ![SVG](svg/WordCount.svg "WordCount (SVG)") The dotted lines represent "unanchored" streams and the number inside the `[]` shows the parallelism hint. -Which was achived by a simple export to console: +This was achived by a simple export to console: *) sampleTopology |> DotGraph.writeToConsole (** -Followed by a convertion into into SVG: +Followed by further conversion into a desired format by piping the markup into GrapViz: ```bash mono samples/WordCount/bin/Release/WordCount graph | dot -Tsvg -o build/WordCount.svg diff --git a/docs/files/img/logo.png b/docs/files/img/logo.png index f3b3b635842ee7fb1d2bcc2cef5ed93cf7a5acc9..c7c0dee9bae23874118c5f9c2a1731c91d273248 100644 GIT binary patch literal 13773 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 literal 13291 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 Sa@31Az*`w*=?Y2Xp#KB-Cp%mK diff --git a/docs/tools/generate.fsx b/docs/tools/generate.fsx index 44879e8..2508dfc 100644 --- a/docs/tools/generate.fsx +++ b/docs/tools/generate.fsx @@ -9,16 +9,16 @@ // for binaries output to root bin folder please add the filename only to the // referenceBinaries list below in order to generate documentation for the binaries. // (This is the original behaviour of ProjectScaffold prior to multi project support) -let referenceBinaries = ["FsShelter.dll"; "FsJson.dll"; "FsLogging.dll"] +let referenceBinaries = ["FsShelter.dll"] // Web site location for the generated documentation let website = "/FsShelter" -let githubLink = "http://github.com/FsShelter/FsShelter" +let githubLink = "http://github.com/Prolucid/FsShelter" // Specify more information about your project let info = [ "project-name", "FsShelter" - "project-author", "Faisal Waris, Eugene Tolmachev" + "project-author", "Eugene Tolmachev" "project-summary", "F# DSL and runtime for Storm topologies" "project-github", githubLink "project-nuget", "http://nuget.org/packages/FsShelter" ] diff --git a/src/FsShelter/Task.fs b/src/FsShelter/Task.fs index 1b7f51e..ce3461c 100644 --- a/src/FsShelter/Task.fs +++ b/src/FsShelter/Task.fs @@ -5,8 +5,10 @@ open System.IO open FsShelter.Multilang open FsShelter.Topology open System - + +/// Logger signature type Log = (unit -> string) -> unit +/// Task signature type Task<'t> = ComponentId -> Runnable<'t> // diagnostics pid shortcut diff --git a/src/FsShelter/Topology.fs b/src/FsShelter/Topology.fs index 6f99170..372d4a8 100644 --- a/src/FsShelter/Topology.fs +++ b/src/FsShelter/Topology.fs @@ -4,18 +4,26 @@ module Topology = open Multilang + /// Tuple id type TupleId = string + /// Stream id type StreamId = string + /// Component id type ComponentId = string + /// Signature for anchoring implementation type ToAnchors = TupleId->TupleId list + /// Signature for pluggable IO implementation type IO<'t> = (unit->Async>)*(OutCommand<'t>->unit) + /// Signature for a final runnable component type Runnable<'t> = IO<'t>->Conf->Async + /// Storm Componend abstraction type Component<'t> = | FuncRef of Runnable<'t> | Shell of command : string * args : string | Java of className : string * args : string list + /// Storm Spout abstraction type Spout<'t> = { MkComp:unit->Component<'t> Parallelism:uint32 @@ -23,6 +31,7 @@ module Topology = } with static member WithConf (s,conf) = {s with Conf = Some conf} static member WithParallelism (s,p) = {s with Parallelism = p} + /// Storm Bolt abstraction type Bolt<'t> = { MkComp:(StreamId->ToAnchors)->Component<'t> Parallelism:uint32 @@ -30,12 +39,14 @@ module Topology = } with static member WithConf (s,conf) = {s with Bolt.Conf = Some conf} static member WithParallelism (s,p) = {s with Bolt.Parallelism = p} + /// Storm stream grouping abstraction type Grouping<'t> = | Shuffle | Fields of names:string list | All | Direct + /// Storm Stream abstraction type Stream<'t> = { Src:ComponentId Dst:ComponentId @@ -44,6 +55,7 @@ module Topology = Schema:string list } + /// Storm Topology abstraction type Topology<'t> = { Name:string Spouts:Map>