Build a command-based architecture in a reactive manner
Janet provides the infrastructure to build a flexible, scalable and resilient
architecture based on actions, RxJava
-powered pipes and the services to execute these actions.
You can learn more about our framework from this presentation
Let's walk through a common flow for the http request:
- Janet is equipped with services to deal with different actions:
Janet janet = new Janet.Builder() .addService(new HttpActionService(API_URL, httpClient, converter)) .addService(new SqlActionService(...)) .addService(new XyzActionService(...)) .build();
-
Our request is described with the
SampleHttpAction
class:@HttpAction(value = "/demo", method = GET) public class SampleHttpAction { @Response SampleData responseData; }
The request is sent/observed with its
ActionPipe
:// create pipe for action class with janet ActionPipe<SampleHttpAction> actionPipe = janet.createPipe(SampleHttpAction.class); // register request result observer actionPipe.observe().subscribe(new ActionStateSubscriber<SampleHttpAction>() .onStart(action -> System.out.println("Request is being sent " + action)) .onProgress((action, progress) -> System.out.println("Request in progress: " + progress)) .onSuccess(action -> System.out.println("Request finished " + action)) .onFail((action, throwable) -> System.err.println("Request failed " + throwable)) ); // send request actionPipe.send(new SampleHttpAction()); // or actionPipe.createObservable(new SampleHttpAction()).subscribe(...)
-
The request is forwarded to the
Janet
instance upon thesend
orsubscribe
call; -
Janet
finds a suitable service to execute an action and routes to it; -
ActionService
knows how to deal with the action and sets up theprogress
/success
/fail
statuses; -
The resulting action is brought back to the
Janet
instance; -
Janet
routes the resulting action with a status to the dedicated pipes; -
The dedicated
ActionPipe
notifies all its observers of the action wrapped with a current status;
So the Janet
instance itself stands for routing and delegating the diff job to other components:
ActionPipe
– an action operator, the only way to send an action and receive a result;ActionService
– knows how to deal with an action;ActionServiceWrapper
– a decorator to add additional logic to the underlying service;
The Janet abilities depend on services. Currently, they include the following:
- HttpActionService to enable the HTTP/HTTPS request execution;
- AsyncActionService to provide support for async protocols, e.g. socket.io;
- CommandActionService to delegate any job back to the command
action
; - AnalyticsActionSerivce to extract analytics out of business/view logic;
Possible solutions:
SqlActionService
, LocationActionService
, BillingActionService
, etc.
The only way to operate with action
is via its ActionPipe
.
It's created for a particular action class:
// Pipe for users list request
ActionPipe<GetUsersAction> usersPipe = janet.createPipe(GetUsersAction.class);
// Pipe for repositories list request
ActionPipe<GetReposAction> repositoriesPipe = janet.createPipe(GetReposAction.class);
An action result is provided via the ActionState
observable:
Observable<ActionState<GetUsersAction>> usersObservable = usersPipe.observe();
ActionState
includes:
- state –
start
/progress
/success
/fail
; - action instance itself;
- progress value;
- exception for the
fail
status
To send a new action for execution:
usersPipe.send(new GetUsersAction());
To combine sending and observing at once:
usersPipe.createObservable(new GetUsersAction()).subscribe(...);
// every other pipe's observer will get result too
Other capabilities:
- observe the latest cached result (aka replay(1)) or clear cache;
- observe success-only results;
- observe base parent actions;
- cancel an action execution;
- create safe read-only pipe forks to listen to results only;
ActionService
is responsible for execution of particular actions.
It defines what actions it's able to process, so janet
knows where to route 'em.
Every service should override 3 methods:
getSupportedAnnotationType()
- defines what actions are processed by their class annotation;<A> void sendInternal(ActionHolder<A> holder)
– is called when a new action is sent to the pipe;<A> void cancel(ActionHolder<A> holder)
– is called when an action in the pipe is canceled;
There are several services to look at:
- Simple impl. -> CommandActionService;
- Complex impl. -> HttpActionService.
A decorator for ActionService
is used to listen to the action status or add the additional intercepting logic.
Abilities:
- Listen to the action flow by statuses;
- Intercept sending completely;
- Intercept the fail status to start a retry;
A simple logging wrapper:
public class LoggingWrapper extends ActionServiceWrapper {
public LoggingWrapper(ActionService actionService) {
super(actionService);
}
@Override protected <A> boolean onInterceptSend(ActionHolder<A> holder) {
System.out.println("send " + holder.action());
return false;
}
@Override protected <A> void onInterceptCancel(ActionHolder<A> holder) {
System.out.println("cancel " + holder.action());
}
@Override protected <A> void onInterceptStart(ActionHolder<A> holder) {
System.out.println("onStart " + holder.action());
}
@Override protected <A> void onInterceptProgress(ActionHolder<A> holder, int progress) {
System.out.println("onProgress " + holder.action() + ", progress " + progress);
}
@Override protected <A> void onInterceptSuccess(ActionHolder<A> holder) {
System.out.println("onSuccess " + holder.action());
}
@Override protected <A> void onInterceptFail(ActionHolder<A> holder, JanetException e) {
System.out.println("onFail " + holder.action());
e.printStackTrace();
}
}
@Provides Janet createJanet() {
return new Janet.Builder()
.addService(new LoggingWrapper(new HttpActionService(API_URL, httpClient, converter)))
.build();
}
Examples:
- Authorize requests via AuthWrapper
- Log requests via TimberWrapper
Possible solutions: caching middleware, the Dagger
injector, retry policy maker, etc.
- Flexibility and scalability. Scale functionality using services;
- Reactive approach for actions interaction by RXJava;
- Throw-safety architecture.
Grab via Maven
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependency>
<groupId>com.github.techery</groupId>
<artifactId>janet</artifactId>
<version>latestVersion</version>
</dependency>
or Gradle:
repositories {
...
maven { url "https://jitpack.io" }
}
dependencies {
compile 'com.github.techery:janet:latestVersion'
}
Copyright (c) 2018 Techery
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
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.