{"id":29376512,"url":"https://github.com/twitter/nodes","last_synced_at":"2025-07-09T22:43:20.451Z","repository":{"id":55539033,"uuid":"65239194","full_name":"twitter/nodes","owner":"twitter","description":" A library to implement asynchronous dependency graphs for services in Java","archived":false,"fork":false,"pushed_at":"2023-04-10T11:33:53.000Z","size":322,"stargazers_count":246,"open_issues_count":10,"forks_count":56,"subscribers_count":28,"default_branch":"master","last_synced_at":"2024-05-09T19:35:13.548Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/twitter.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-08-08T21:04:10.000Z","updated_at":"2024-05-09T19:35:13.548Z","dependencies_parsed_at":"2023-02-09T20:25:14.406Z","dependency_job_id":null,"html_url":"https://github.com/twitter/nodes","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/twitter/nodes","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twitter%2Fnodes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twitter%2Fnodes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twitter%2Fnodes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twitter%2Fnodes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/twitter","download_url":"https://codeload.github.com/twitter/nodes/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twitter%2Fnodes/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264504616,"owners_count":23618831,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-07-09T22:43:19.386Z","updated_at":"2025-07-09T22:43:20.444Z","avatar_url":"https://github.com/twitter.png","language":"Java","funding_links":[],"categories":["并发编程"],"sub_categories":[],"readme":"[![Build Status](https://travis-ci.org/twitter/nodes.svg?branch=master)](https://travis-ci.org/twitter/nodes) [![Maven Central](https://img.shields.io/maven-central/v/com.twitter/twitter-nodes.svg)](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.twitter%22%20AND%20a%3A%22nodes%22)\n\nNodes\n============================\n\nNodes is a library to implement asynchronous dependency graphs for services in Java.\n\n## Background\n\nWhen you write an asynchronous service, like an RPC server, or just a non-blocking library to do some work, you may need to implement an interface like this:\n\n```java\nFuture\u003cResponse\u003e processRequest(Request req)\n```\n\nYour logic goes inside `processRequest()`, you may do some local computation here, call some external services, or even run some code in another thread. The logic dependencies in these code could be complicated. [Finagle](https://twitter.github.io/finagle/) provides a good paradigm for [concurrent programming with Futures](http://twitter.github.io/finagle/guide/Futures.html). The `Future` class is a great building block, it decouples the execution logic and the thread scheduling, so the developer can focus on the logic dependencies rather than their actual execution. However, this library was written in Scala and isn't exactly Java friendly. It naturally involves a lot of callbacks and repeated function signatures. When it comes to waiting on multiple Futures, the code gets ugly very fast. Nodes is a Java library that aims to solve these problems, making the asynchronous code easier to read, to maintain and to test in Java.\n\n## Basic Concepts\nA node is an asynchronous processing unit. It takes multiple asynchronous input nodes (dependencies), and produces a single output. It will only start executing when all inputs are ready. A node object is also a handle to its output. Like `Future\u003cA\u003e` in Finagle, `Node\u003cA\u003e` represents an asynchronously computed data of type A. Actually `Node` and `Future` are mutually convertible. The tutorials below will show you how to create nodes and assemble them into a dependency graph.\n\nAnother way to understand nodes is to consider them asynchronous functions, every node with input of type A, B, C and return type of X can be thought as a function with signature:\n\n```java\nFuture\u003cX\u003e process(Future\u003cA\u003e a, Future\u003cB\u003e b, Future\u003cC\u003e c)\n```\n\n## Tutorials\n\nFor quick examples, please see [src/main/java/com/twitter/nodes_examples](src/main/java/com/twitter/nodes_examples).\n\n### Creating a Node\n\nIf your node has a fixed number (more than 1) of dependencies, the most common way to create a Node is as follows:\n\n```java\n// A node that produces an integer as output \npublic class MyNode extends Node\u003cInteger\u003e {\n  public enum D {  // \"D\" is a convention\n    DEP1,\n    DEP2,\n    @OptionalDep DEP3  // this dependency is optional, may be omitted\n  }\n\n  @Override\n  protected Future\u003cInteger\u003e evaluate() throws Exception { \n    // To get the value of a dependency nodes, use getDep(key)\n    Type1 value1 = getDep(D.DEP1);\n    Type2 value2 = getDep(D.DEP2);\n    Type3 value3 = getDep(D.DEP3, someDefault);\n    Integer result = compute(value1, value2, value3);\n    return Future.value(result);\n  }\n}\n```\n\nYou need write a `public` class extending `Node` (or `NullableNode`, more on this later) with a certain type, inside which you define enums for all your list of dependencies (the name convention is `D`). When you instantiate the node, you will use these enums to mark out your dependencies.\n\nA dependency is by default *required* (without any marking), which means throwing an exception in it would fail the depending node directly and cause them not to get executed at all. Optional dependencies are marked as `@OptionalDep`, allowing them to be omitted or to fail.\n\n\u003e **Under the hood**: inside Node uses an `EnumMap` to store all dependency nodes. Even if you don't define your custom enum, there is a default one called `Node.DefaultDependencyEnum` that has 16 entries: `DEP0`, `DEP1` ... `DEP15`. If you directly use the constructor of the parent class to specify all your depdnencies, you will need to pass in a list of nodes, which will be mapped to these default enums in the order they appear.\n\nYou need to implement the `evaluate()` method, which will only be called when all dependencies are ready, and called only once. You can get values for each of the dependencies using `getDep()` and use them in your computation. At the end, you are supposed to return a `Future` object with desired return type. If you don't have any asynchronous processing inside, you can just wrap your output with `Future.value()`. However, you can also call other asynchronous services like a remote server, or submit tasks to another thread, and pass back the `Future` object you acquired from them directly.\n\nThere is always a simple way to create a multiple-dependency node inline with Java 8 lambda functions, with support for up to 4 inputs. See [Map with Multiple Inputs](#map-multiple) for more details.\n\n#### Instantiating the Node\n\nTo instantiate your node with enum-based dependencies:\n\n```java\nNode\u003cInteger\u003e resultNode = Node.build(\n    MyNode.class,\n    MyNode.D.DEP1, node1,\n    MyNode.D.DEP2, node2,\n    MyNode.D.DEP3, node3);\n```\n\nYou can omit the optional dependency without causing any runtime exception.\n\n```java\nNode\u003cInteger\u003e resultNode = Node.build(\n    MyNode.class,\n    MyNode.D.DEP1, node1,\n    MyNode.D.DEP2, node2);\n```\n\nOr the input can even be a failure, for example:\n\n```java\nNode\u003cInteger\u003e resultNode = Node.build(\n    MyNode.class,\n    MyNode.D.DEP1, node1,\n    MyNode.D.DEP2, node2,\n    MyNode.D.DEP3, Node.fail(new Exception()));\n```\n\n#### NullableNode\n\nNormally a node shouldn't return a `null` value, an exception would be thrown if it encounters a null output. You should try to use the control flow methods (see below) to manage the execution and make `null` value unnecessary, or utilize `Optional\u003cA\u003e` to represent return values that can be null. However, we also provide `NullableNode` which you can extend from, they can return `null` without causing any exception. The reason we make default `Node` class null-unfriendly is to make it easy to reason what went wrong during the execution, and not to confuse an error with a non-existent value, but overall this is a matter of style. The `NullableNode` is convenient but you should use it with care.\n\n#### ServiceNode\n\nMost of the nodes are just doing local computations. For nodes that calls any asynchronous service, be it an external Thrift RPC server, an HTTP server, or just a in-process scheduler that runs tasks in a thread pool, you can extend from `ServiceNode`, which provides some convenience for dealing with services. Rather than implementing `evaluate()`, you need to implement `buildRequest()` and `getService()`.\n\nThe method `buildRequst()` builds the request for current service call, in the parent class it's called in `evaluate()` so all dependencies should already be ready.\n\nThe method `getService()` gets a Finagle `Service` object, with has a method `Future\u003cResponse\u003e apply(Request req)`. You can get this object from some factory, some registry, or simply from some static variables.\n\n```java\npublic class NodeServiceNode extends ServiceNode\u003cResponse\u003e {\n  @Override\n  public Service\u003cRequest, Response\u003e getService() {\n    ...\n  }\n  \n  @Override\n  public Request buildRequest() {\n    // this is called inside evaluate(), you can get all your dependencies\n    // the same way, or just get them from your own member variable.\n  }\n}\n```\n\nTo specify the dependencies, you can either use the same enum-based solution described above, or directly use Node's own constructor (which takes a list of nodes). You can even implement your own constructor if it takes some special inputs more than dependending nodes.\n\n### Executing a Dependency Graph\n\nA node (which also denotes a dependency tree rooted at it) doesn't get executed automatically after they are constructed. The instantiation of the node only defines the logic dependency, but nothing has executed yet. To start running, you need to call `apply()`:\n\n```java\nFuture\u003cA\u003e aFuture = aNode.apply();\n```\n\nThis creates a `Future` of the node, which you can wait on. This will trigger the evaluation of all its dependencies and cause their `apply()` to get called, and in turn trigger the execution recursively. You start from the root of a tree and it eventually reaches all leaves, which are inputs. Only the nodes reachable by walking the dependency links will be executed.\n\nYou can just pass this `Future` object to anywhere it's needed, like inside the `processRequest()` method of your server implementation in the example at the beginning. If you want to block on it and get its value, you can call:\n\n```java\nA a = Await.result(aNode.apply(), Duration.ofSeconds(2));\n```\n\n### Sinks\n\nAs mentioned above, only the node reachable by the dependency link from an executing node will ever be executed. However, there are situations where we want a node to execute but no one is waiting for its result (like you want to start and forget a logging process after a request has been processed, but you can respond without waiting for that logging work to be done). You can implement this logic with Sinks, a set of nodes you attach to another node which gets executed *after* it has finished. It's like a reverse dependency.\n\n```java \nNode\u003cInteger\u003e resultNode = Node.builder(MyNode.class)\n  .withSinkNodes(s1, s2, ...)\n  .withDependencies(\n    MyNode.D.DEP1, node1,\n    MyNode.D.DEP2, node2,\n    MyNode.D.DEP3, node3);\n    \n// You can also set the sink after the node is created\nresultNode.setSinkNodes(s1, s2, ...);\n```\n\n### Deciders\n\nNodes can be selectively turned on and off by \"Deciders\", which is just a simple `Supplier` object that produces a true/false value. You can use this to implement runtime control of your nodes. Having supplier return `false` would cause node not to execute and return failure.\n\n```java \n// You can add deciders and sinks as well\nNode\u003cInteger\u003e resultNode = Node.builder(MyNode.class)\n  .withDeciderSupplier(someDeciderSupplier)\n  .withDependencies(\n    MyNode.D.DEP1, node1,\n    MyNode.D.DEP2, node2,\n    MyNode.D.DEP3, node3);\n```\n\n### Nodes without a Body\n\nYou node doesn't need to always have a computation class, it can directly wrap a value or a `Future` object.\n\n```java\n// To wrap node around an immediate value, without any computation process:\nNode\u003cA\u003e nodeA = Node.value(somethingOfTypeA);\n\n// To wrap node around a Finagle Future of type A\nFuture\u003cA\u003e futureA = ...;\nNode\u003cA\u003e nodeA = Node.wrapFuture(futureA);\n```\n\nAs we mentioned above, a `Node` can be converted to a `Future` by just calling `apply()`:\n\n```java\nNode\u003cA\u003e nodeA = ...;\nFuture\u003cA\u003e futureA = nodeA.apply();\n```\n\nA future may fail and return exception, if it's used as a required dependency of another node, it would fail that node. Of course you can make it an optional dependency, but it may not always be feasible when you are calling some existing node code. You can make create a \"safe\" node masking `Future`'s failure. All failures will simply become `null` result.\n\n```java\nNode\u003cA\u003e nodeA = ...;  // this may fail\nFuture\u003cA\u003e futureA = nodeA.toFutureSafe();\n```\n\n#### Node Literals\n\nThere are some node literals for your convenience.\n\n```java\nNode.TRUE           // Node\u003cBoolean\u003e with True value\nNode.FALSE          // Node\u003cBoolean\u003e with False value\nNode.NULL_NODE      // typeless Node with a null value\nNode.\u003cT\u003enoValue()   // typed Node with a null value\n\n// To create a literal failed node\nNode.fail(Throwable t)\n```\n\n### Subgraph\n\nPutting your whole graph in a single file is probably not convenient. Subgraph provides a way to organize your graph. Subgraph is a subset of the graph (duh), its inputs/outputs consist of the union of all inputs/outputs of the nodes at the boundary, so it can have more than 1 input and more than 1 output.\n\nSubgraph doesn't have any effect on the execution of the nodes, it's just a way to organize nodes into a more reusable way. Below is a recommended style to implement Subgraphs.\n\n```java\npublic class MySubgraph extends Subgraph {\n  public final Node\u003cType1\u003e exposedNode1; \n  public final Node\u003cType2\u003e exposedNode2; \n\n  public MySubgraph(Node\u003cA\u003e inputNode1, \n                    Node\u003cB\u003e inputNode2,\n                    Node\u003cC\u003e inputNode3) {\n                    \n    // ... all the wiring\n    \n    this.exposedNode1 = ... \n    this.exposedNode2 = ...\n \n    // only needed if you need to generate DOT graph\n    markExposedNodes();\n  }\n}\n \n// Constructing two copies of the same subgraph, using different inputs:\nMySubgraph s1 = new MySubgraph(node1, node2, node3);\nMySubgraph s2 = new MySubgraph(node4, node5, node6);\n\n// Access its produced outputs referencing the public members:\n//   s1.exposedNode1\n//   s2.exposedNode2\n```\n\nThe suggested style is:\n\n* Subclass from Subgraph\n* All input nodes as constructor arguments\n* All output nodes as public member variables\n* You can also have other non-node input to configure the subgraph and provide other information.\n\nThe existence of subgraphs is transparent to the final dependency graph. You can always flatten a subgraph at its caller side, or split a subgraph into more smaller subgraphs if that helps with your modularity.\n\nThe call to the `markExposedNodes()` method at the end is purely for bookkeeping, this will help you generate better visualization/debug message, but it has no impact on the execution. Even if you forget to call it, it not a big deal.\n\n### Node Transformations\n\nFor nodes with only a single input/dependency, it's cumbersome to create a whole new node class. We provide many syntactic sugar for you to do this kind of transformations easily.\n\n#### map and flatMap\n\nYou can convert a node of type A to type B using a map. You only need to provide a function (in the form of a name string and a Java function, or a `NamedFunction` object). You can also do a `flatMap` where your function returns a `Future\u003cB\u003e` rather than straight type B, so you can call other asynchronous process inside.\n\n```java\n// To convert the node of one type to another, synchronously\nNode\u003cB\u003e bNode = aNode.map(namedFunction);      // map with a NamedFunction\u003cA,B\u003e\n\nNode\u003cB\u003e bNode = aNode.mapOnSuccess(func);   // only when aNode is successfully done, or emit null\nNode\u003cB\u003e bNode = aNode.mapWithDeciderSupplier(ds, func);   // only when decider is on\n \n// To convert the node of one type to another, asynchronously\nNode\u003cB\u003e bNode = aNode.flatMap(func);   // where func is an instance of NamedFunction\u003cA, Future\u003cB\u003e\u003e\nNode\u003cB\u003e bNode = aNode.flatMapOnSuccess(func);   // only when aNode is successfully done, or emit null\nNode\u003cB\u003e bNode = aNode.flatMapWithDeciderSupplier(ds, func);   // only when aNode is successfully done, or emit null\n \n// To get a boolean value out of node:\nNode\u003cBoolean\u003e booleanNode = aNode.predicate(pred);  // where pred is a NamedPredicate\u003cA\u003e\n \n// Java 8 support: you can also use Java 8 function in all above cases, except that you need to provide a name\n// as the first argument. The name is only used for tracking and debugging, and ignored in production.\n// For example:\nNode\u003cB\u003e bNode = aNode.map(\"name\", a -\u003e doSomethingTo(a));\nNode\u003cB\u003e bNode = aNode.map(\"name\", a -\u003e { ... });\nNode\u003cB\u003e bNode = aNode.map(\"name\", SomeClass::staticMethodOnA);\nNode\u003cB\u003e bNode = aNode.map(\"name\", someObject::instanceMethodOnA);\n// same applies to .mapOnSuccess(), .flatMap(), etc\n```\n\n#### \u003ca name=\"map-multiple\"\u003e\u003c/a\u003e Map with Multiple Inputs\nActually we also support map with multiple inputs, this can be used to implement simple multi-dependency node as well, rather than using enum-based dependency definitions:\n\n```java\nNode\u003cX\u003e nodeX = Node.map2(\n    \"map2\", nodeA, nodeB, (a, b) -\u003e {...})\n\nNode\u003cX\u003e nodeX = Node.map3(\n    \"map3\", nodeA, nodeB, nodeC, (a, b, c) -\u003e {...})\n\nNode\u003cX\u003e nodeX = Node.map4(\n    \"map4\", nodeA, nodeB, nodeC, nodeD, (a, b, c, d) -\u003e {...})\n```\n\nAlso there are `flatMap` versions, the function passed in should return a `Future\u003cX\u003e` instead.\n\nYou can use these mappers/flatMappers to implement simple nodes with no optional dependencies. You can test them by just testing the normal function passed in.\n\n#### Bolean operations\n\nWe provide some convenient methods for manipulating nodes wiht boolean values:\n\n```java\n// Boolean nodes\nNode\u003cBoolean\u003e booleaNode = AndNode.create(b1Node, b2Node, b3Node, ...);\nNode\u003cBoolean\u003e booleaNode = AndNode.createLazy(b1Node, b2Node, b3Node, ...);  // short-circuiting, will not evaluate later nodes\nNode\u003cBoolean\u003e booleaNode = OrNode.create(b1Node, b2Node, b3Node, ...);\nNode\u003cBoolean\u003e booleaNode = OrNode.createLazy(b1Node, b2Node, b3Node, ...);  // short-circuiting, ditto\nNode\u003cBoolean\u003e booleaNode = NotNode.create(b1Node);\n```\n\nNote that the \"lazy\" version would short-circuit the execution, not all input nodes are to be executed, only the first input is its real dependency.\n\n\n#### Other transformations\n\nWe also have some other transformations that deal with 2 nodes.\n \n```java\nNode\u003cA\u003e nodeA = ...;\nNode\u003cB\u003e nodeB = ...;\n\nNode\u003cList\u003cA\u003e\u003e listNode = NodeUtils.asList(nodeA);\nNode\u003cPair\u003cA, B\u003e\u003e pairNode = NodeUtils.asPair(nodeA, nodeB);\n```\n\nYou can also convert a list of nodes (of the same type) to a node with the list type. This is handy when you need to call unknown number of async processes and handle all of their responses together.\n\n```java\nList\u003cNode\u003cA\u003e\u003e listOfNodeA = ...\nNode\u003cList\u003cA\u003e\u003e node = Node.collect(listOfNodeA);\n```\n\nSimilarly, there is a Map based version:\n\n```java\nMap\u003cA, Node\u003cB\u003e\u003e nodesByKey = ...\nNode\u003cMap\u003cA, B\u003e\u003e node = Node.collect(nodesByKey);\n```\n\nYou can also apply some transformation to each entry in a list under a node, and collect all results:\n\n```java\nNode\u003cList\u003cA\u003e\u003e alistNode = ...\nNode\u003cList\u003cB\u003e\u003e blistNode = Node.splitAndCollect(\n    alistNode, \"functionname\", a -\u003e { ... return something Node\u003cB\u003e ... });\n```\n\nYou can take a look at their implementation and you can also build your own fancy node transformations.\n\n### Control Flow with Nodes\n\nYou can implement if-then-else logic with Nodes. There is also syntactic sugar to represent some other common condition checks, like whether a node has finished running successfully (more on \"successful nodes\" later).\n\nSimple if-then-else control flow\n\n```java \n// An if-else switch between two nodes:\nNode\u003cC\u003e cNode = Node.ifThenElse(booleanNode, cNode1, cNode2);\nNode\u003cC\u003e cNode = Node.ifThen(booleanNode, cNode1);  // equivalent to cNode2 being Node.noValue()\n\n// Simpler if-else\nNode\u003cC\u003e cNode = xNode.when(booleanNode);     // gets xNode if booleanNode emits true, or Node.noValue()\nNode\u003cC\u003e cNode = xNode.unless(booleanNode);   // gets xNode if booleanNode emits false, or Node.noValue()\n```\nSuccess-based control flow\n\n```java\n// On success\nNode\u003cC\u003e cNode = xNode.whenSuccess(anyNode);  // gets xNode if anyNode is success, or Node.noValue();\nNode\u003cC\u003e cNode = xNode.orElse(yNode);  // gets xNode if xNode is a success, otherwise gets yNode\n```\n\n### Logging and Debug Messages\n\nNodes provides a simple framework for collecting debug messages in the asychrounous execution of the dependency graph. You can use the `DebugManager` class with a [flexibly-scoped thread-local](https://twitter.github.io/util/docs/#com.twitter.util.Local) `DebugMessageBuilder` inside. For each execution of a graph, you can set a new `DebugMessageBuilder` and all debug messages will be collected over there.\n\n```java\nDebugManager.update(new DebugMessageBuilder(DebugLevel.DEBUG_DETAILED));\n```\n\nYou can choose from multiple `DebugLevels`, When you produce the debug message, only those no higher than the set level would be collected.\n\nAnywhere in your node's `evaluate()` method or other non-static methods, you can call following member methods to append a message to the current `DebugMessageBuilder`. It supports `String.format()` style formatters. A line break will be added automatically after each call.\n\n```java\nbasic(\"current score: %d\", score);\ndetailed(\"input type = %, parameters = %s\", type, params);\nverbose(\"server full response: %s\", response);\nverbose2(\"detailed debug info: %s\", stuff);\nverbose3(\"super detailed debug info: %s\", stuff);\n```\n\nIf you are not in the scope of a Node subclass, you can still append message by calling similar methods in DebugManager.\n\n```java\nDebugManager.basic(...)\nDebugManager.detailed(...)\nDebugManager.verbose(...)\nDebugManager.verbose2(...)\nDebugManager.verbose3(...)\n```\n\nAll these messages will be properly prefixed with the node name and timestamp. Node itself also produces some debug messages to mark the beginning and end of its execution, storing the time spent in the node, etc.\n\nTo get the debug message after the node has finish executing, just call `DebugManager.getDebugMessage()`.\n\nIf you run the sample code in `src/main/java/com/twitter/nodes_examples/search/SearchExampleMain.java', you will get debug messages like:\n\n```\n[5184] NODE [hasQuery]: Start\n[5185] NODE [hasQuery]: End (52/0 ms)\n[5185] NODE [IF::hasQuery(BuildResponseNode, null)]: Start\n[5196] NODE [hasUserId]: Start\n[5196] NODE [getQuery]: Start\n[5196] NODE [getNumResults]: Start\n[5196] NODE [hasUserId]: End (2/0 ms)\n[5196] NODE [getQuery]: End (1/0 ms)\n[5197] NODE [getNumResults]: End (2/0 ms)\n[5197] NODE [IF::hasUserId(UserScoreServiceNode[?], defaultUserScore)]: Start\n[5197] NODE [SearchIndexNode]: Start\n[5206] NODE [getUserId]: Start\n[5206] NODE [getUserId]: End (1/0 ms)\n[5207] NODE [UserScoreServiceNode[?]]: Start\n[5207] NODE [UserScoreServiceNode[?]]: built service request: 777\n[5854] NODE [SearchIndexNode]: End (659/8 ms)\n...\n```\n\nThe number at the beginning of each line is the last 4 digits of current time in milliseconds, so you have a sense when each step was run.\n\n### Naming Conventions\n\nThere are some naming conventions for node code we'd like to suggest:\n\n* Start your node class and subgraph class name with a verb, like `CreateRecordNode` or `ResolveGeoLocationSubgraph`.\n* Name your node object as a noun and always end with `Node`, describing the product rather than the process, like `recordNode`.\n\n### Testing\n\nIt's easy to write tests for nodes. Just instantiate them and test their output with specific inputs.\n\n* if you have any optional input, make sure you have some tests without these inputs.\n* for subgraphs, try to test the wiring, or the control flow implemented in the graph, rather than the logic of nodes inside. \n\n\n### Visualzation\n\nYou can visualize your node by generating a [DOT](http://www.graphviz.org/Documentation.php) file.\n\n```java\n// for nodes\nString dot = mynode.toDotGraph();\n\n// for subgraphs\nString dot = mygraph.toDotGraph();\n\n// You can save these strings to a file to be rendered by other tools.\nFiles.write(mygraph.toDotGraph().getBytes(), new File(\"graph.dot\"));\n```\n\nYou can render the generated file using Graphviz ([download here](http://www.graphviz.org/Download_macos.php), you may also need [X11](https://www.xquartz.org/)). Check out the [DOT language reference](http://www.graphviz.org/Documentation.php) if you want to understand the generated file, or read [NodeDotGraphGenerator.java](src/main/java/com/twitter/nodes/NodeDotGraphGenerator.java). A rendered graph looks like this:\n\n![DOT dependency diagram](src/main/java/com/twitter/nodes_examples/search/graph.png \"Rendered Depependency Graph from example code\")\n\nThe type of the node is indicated by its shape, while the optionality of dependencies are represented by the edges connecting them.\n\nFor nodes:\n\n* pink box: value nodes\n* green-yellow trapezoid: transform node\n* green-blue inverted trapezoid: predicate switch node (with \"condition\" and true/false branch), created by `Node.ifThen()` or `Node.ifThenElse()`\n* gray double-edged box: service node\n* white square box: other normal nodes\n\nFor edges (dependencies):\n\n* solid: required dependency\n* dashed: optional dependency\n* label: dependency name, useful especially if you use enum style suggested above.\n\nTo make your graph look nice, always remember naming TransformNode (give it method-like name) and ValueNode (give it variable like name).\n\n\n## Download, Build and Test\n\nYou can download the code by cloning this repo:\n\n```\ngit clone https://github.com/twitter/nodes\n```\n\nGo into the newly created `nodes` directory, to compile and run tests:\n\n```\nmvn compile\nmvn test\n```\n\nYou may see many exception stacktrace in the output, ignore them, they are expected.\n\nTo create a `.jar` file for nodes, run:\n\n```\nmvn package\n```\n\nand you shall find the `.jar` file at `dist/lib/nodes-1.0.0.jar`.\n\nThis library depends on following Twitter libraries:\n\n* [Twitter util](https://github.com/twitter/util) (util-core, util-logging)\n* [Finagle](https://github.com/twitter/finagle) (finagle-core)\n\nPlease see `pom.xml` for other dependencies.\n\n## Support\n\nYou can join our mailing list `twitter-nodes@googlegroups.com` on [Google Groups](https://groups.google.com/forum/#!forum/twitter-nodes), you can check out the archived threads on web.\n\n## Copyright and License\n\nCopyright 2016 Twitter, Inc and other contributors\n\nLicensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftwitter%2Fnodes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftwitter%2Fnodes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftwitter%2Fnodes/lists"}