Reactive Programming is asynchronous, non-blocking, highly responsive processing. The core idea is that Everything is stream. From this perspective, we can regard the Design idea of responsive programming as stream-oriented Design, that is, flow-oriented Design.

Just as object-oriented design is based on objects and functional programming is based on functions, responsive programming should be based on flows. This led to fundamental changes in design thinking, including:

  • Use the flow as the modeling element
  • The flow has a loosely coupled upstream and downstream relationship
  • Use flow as the unit of reuse
  • Convection transforms, calculates, merges, and splits

In the Rx framework, a stream is an Observable or Flowable. For example, if we want to count the number of words on a web page, the source of the stream is the fetch of the web content, which is the type of Observable

. As for statistical operation, it needs to go through two stages of word segmentation and word count, which can be regarded as convection conversion and operation:

Flowable.fromFuture(pageContent)

.flatMap(content -> Flowable.fromArray(s.split(" ")))

.map(w -> new Pair<>(w, 1))

.groupBy(Pair::getKey);

Copy the code

Since the Rx framework provides operators such as Merge, combineLatest, and ZIP to combine multiple streams, we can create separate streams and then use these operators to merge them, or vice versa. This results in as many reusable streams of atoms as possible. For example, for the UI click operation and the response response, we can create two separate streams and combine them using combineLatest. Whichever stream emits data, it combines the most recent data emitted by the two streams and performs the specified function.

The Graph proposed by Akka Stream better embodies the idea of flows as modeling elements. As long as we plan our processes and think about the inputs and outputs of the steps that make up these processes, we can model these steps as Source, Sink, Flow, and fan-in, fan-out, and BidiFlow respectively, as shown below:

For example, for bank transaction business, if we need to execute the following process:

  • Get all accounts based on the given account number
  • Obtain all backingTransactions and settlement transactions simultaneously by account.
  • These transactions are validated after they are obtained
  • The verified data are used for auditing and net calculation respectively

When we domatically model this process, we can actually draw a visualization that represents the Graph in Akka Streams:

With these visualizations, we can model the nodes in these graphs as Graph Shapes in Akka Streams. Broadcast and Merge streams correspond to Broadcast fan-out and Merge fan-in of the framework. Except for the entry accountNos as the Source and Sink for the final audit and net worth calculation, the remaining nodes are of Flow type. The implementation code is as follows:

  val graph = RunnableGraph.fromGraph(GraphDSL.create(netTxnSink) { implicit b => ms =>

    import GraphDSL.Implicits._



    val accountBroadcast = b.add(Broadcast[Account](2))

    val txnBroadcast = b.add(Broadcast[Transaction](2))

    val merge = b.add(Merge[Transaction](2))



    val accounts = Flow[String].map(queryAccount(_, AccountRepository))

    val bankingTxns = Flow[Account].mapConcat(getBankingTransactions)

    val settlementTxns = Flow[Account].mapConcat(getSettlementTransactions)

    val validation = Flow[Transaction].map(validate)



    accountNos ~> accounts ~> accountBroadcast ~> bankingTxns ~> merge ~> validation ~> txnBroadcast ~> ms

                              accountBroadcast ~> settlementTxns ~> merge

    txnBroadcast ~> audit

    ClosedShape

  })

Copy the code

The Scala language is more consistent with THE semantics of DSL because of the syntax sugar provided by operator overloading, implicit conversion and so on. For example, the ~> symbol in the code clearly expresses the direction of the data flow and what nodes it flows through. Crucially, these Flow definitions are not strongly coupled to each other, and you can connect flows to flows using combinatorial operators as long as you ensure that the data being transferred is correct. Such flows are also Lazy and can be reused efficiently.

Therefore, with responsive programming, you need to design around flow and combine it as a very important reuse element. This is where I got the idea for what I call stream-oriented Design.