Upgrade to Pro — share decks privately, control downloads, hide ads and more …

"Build command-based architecture in reactive manner" from Alexander Kozlov

"Build command-based architecture in reactive manner" from Alexander Kozlov

Main topic is how to build flexible, scalable and resilient architecture based on command-like actions, RxJava-powered pipes and services. Also we gonna discuss and show how one can effectively split service layer from business logic.

uaMobiTech

July 28, 2016
Tweet

More Decks by uaMobiTech

Other Decks in Programming

Transcript

  1. Usual Service Layer • Communication with server side • Processing

    business layer • Working with entities (storage) • Security rules • Other use cases
  2. Usual Service Layer • Communication with server side • Processing

    business layer • Working with entities (storage) • Security rules • Other use cases ➡ NetworkManager ➡ UserManager, ChatManager ➡ StorageManager ➡ SecurityManager ➡ (*)Manager
  3. Usual Service Layer • Communication with server side • Processing

    business layer • Working with entities (storage) • Security rules • Other use cases ➡ NetworkManager ➡ UserManager, ChatManager ➡ StorageManager ➡ SecurityManager ➡ (*)Manager Use Cases, Repositories, Adapters Interactors +
  4. Usual Service Layer • Communication with server side • Processing

    business layer • Working with entities (storage) • Security rules • Other use cases } Actions
  5. When we think about actions NetworkManager UserManager StorageManager LoginAction RegisterAction

    UsersAction AddFriendAction DeleteFriendAction StoreFriendsAction
  6. Problem of different approaches • async-http • Goro • EventBus

    • DBFlow • Retrofit 2 • Goro • RxJava • Realm • Retrofit • RoboSpice • RxJava • SnappyDB
  7. Based on actions Flexibility and scalability. 
 Scale functionality using

    services Reactive approach for actions
 interaction by RXJava Throw-safety architecture We made Janet
  8. Janet Flow Result Action Send ActionPipe Subscribe Janet Service Result

    Result PROGRESS SUCCESS FAIL START Result = Action + Status
  9. Janet Flow ActionState Action Send ActionPipe Subscribe Janet Services ActionState

    ActionState PROGRESS SUCCESS FAIL START ActionState = Action + Status
  10. Services Janet janet = new Janet.Builder()
 .addService(new HttpActionService(API_URL, httpClient, converter))

    
 .addService(new SqlActionService(...))
 .addService(new XyzActionService(...))
 .build();
  11. Services To add new ActionService you should override 3 methods

    • getSupportedAnnotationType - defines what actions are processed by their class annotation; • sendInternal – is called upon new action is sent to pipe; • cancel – is called upon action is canceled from pipe;
  12. Janet Services • HttpActionService to provide HTTP/HTTPS requests execution •

    AsyncActionService to provide support for async protocols, e.g. socket.io • CommandActionService to delegate job back to command action
  13. Janet Action @HttpAction(value = “/demo/{id}”, method = GET) 
 public

    class SampleHttpAction {
 @Query("param") String param; @Path("id") String id;
 @Response SampleData responseData;
 } Processed by HttpActionService
 See github.com/techery/janet-http
  14. How to use? // Create pipe for action class with

    janet ActionPipe<SampleHttpAction> actionPipe = janet.createPipe(SampleHttpAction.class);
  15. How to use? // 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 -> {})
 .onProgress((action, progress) -> {})
 .onSuccess(action -> {})
 .onFail((action, throwable) -> {})
 );
  16. How to use? // 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 -> {})
 .onProgress((action, progress) -> {})
 .onSuccess(action -> {})
 .onFail((action, throwable) -> {})
 ); // Send action actionPipe.send(new SampleHttpAction()); // or actionPipe.createObservable(new SampleHttpAction()).subscribe();
  17. How to use? // Create pipe for action class with

    janet ActionPipe<SampleHttpAction> actionPipe = janet.createPipe(SampleHttpAction.class); // Register request result observer
 actionPipe.observeWithReplay() .subscribe(new ActionStateSubscriber<SampleHttpAction>()
 .onStart(action -> {})
 .onProgress((action, progress) -> {})
 .onSuccess(action -> {})
 .onFail((action, throwable) -> {})
 ); // Send action actionPipe.send(new SampleHttpAction()); // or actionPipe.createObservable(new SampleHttpAction()).subscribe();
  18. How to use? // Create pipe for action class with

    janet ActionPipe<SampleHttpAction> actionPipe = janet.createPipe(SampleHttpAction.class); // Register request result observer
 actionPipe.observeSuccessWithReplay() .subscribe((action) -> doSomething(action)); // Send action actionPipe.send(new SampleHttpAction()); // or actionPipe.createObservable(new SampleHttpAction()).subscribe();
  19. How to use? @Override
 protected void onResume() {
 super.onResume();
 usersPipe.observeWithReplay()


    .compose(bindToLifecycle())
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(new ActionStateSubscriber<UsersAction>()
 .onStart((action) -> showProgressLoading(true))
 .onSuccess(action -> {
 updateData(action.getResponse());
 showProgressLoading(false);
 })
 .onFail((action, throwable) -> showProgressLoading(false)));
 }
 
 @OnClick(R.id.button)
 public void loadUsers(){
 usersPipe.send(new UsersAction());
 }
  20. How to test? @Before
 public void setup() throws JanetException {


    MockHttpActionService httpActionService = new MockHttpActionService.Builder()
 .bind(new MockHttpActionService.Response(200)
 .body(testUser), request -> request.getUrl().endsWith("user"))
 .build();
 Janet janet = new Janet.Builder().addService(httpActionService).build();
 apiInteractor = new ApiInteractor(janet);
 }
 
 @Test
 public void loadUsers() {
 TestSubscriber<ActionState<UserAction>> subscriber = new TestSubscriber<>();
 apiInteractor.userActionPipe()
 .observe()
 .subscribe(subscriber);
 apiInteractor.userActionPipe().send(new UserAction());
 assertActionSuccess(subscriber, action -> action.getUser().equals(testUser));
 }

  21. ActionServiceWrapper Abilities: • Pre/post processing • Additional business logic •

    Intercepting, re-routing Decorator for ActionService is used to listen for action status or add additional intercepting logic. Possible solutions: caching middleware, Dagger injector, retry policy, etc.
  22. ActionServiceWrapper public class AuthServiceWrapper extends ActionServiceWrapper {
 
 private final

    Storage storage;
 
 public AuthServiceWrapper(HttpActionService actionService, PreferenceWrapper prefs) {
 super(actionService);
 this.storage = prefs;
 }
 
 @Override protected <A> boolean onInterceptSend(ActionHolder<A> holder) {
 if (holder.action() instanceof BaseHttpAction) {
 String authToken = storage.getToken();
 if (!TextUtils.isEmpty(authToken)) {
 ((BaseHttpAction) holder.action()).setToken(authToken);
 }
 }
 return false;
 }

  23. Command • Custom operations • Business use cases • Janet

    action chains Processed by CommandActionService See https://github.com/techery/janet-command
  24. Command @CommandAction
 public class SaveBitmapCommand extends Command<File> {
 
 private

    final Bitmap bitmap;
 
 public SaveBitmapCommand(Bitmap bitmap) {
 this.bitmap = bitmap;
 }
 
 @Override
 protected void run(CommandCallback<File> callback) throws Throwable {
 File file = new File(........);
 FileOutputStream stream = new FileOutputStream(file);
 try {
 boolean saved = bitmap.compress(Bitmap.CompressFormat.JPEG, 90, stream);
 if (saved) {
 callback.onSuccess(file);
 } else {
 callback.onFail(new IllegalStateException("Compressing error"));
 }
 } finally {
 stream.close();
 }
 }
  25. Command @CommandAction
 public class UploadBitmapCommand extends Command<UploadResult> {
 
 @Inject


    Janet janet; private final Bitmap bitmap;
 
 public UploadBitmapCommand(Bitmap bitmap) {
 this.bitmap = bitmap;
 }
 
 @Override
 protected void run(CommandCallback<UploadResult> callback) throws Throwable { janet.createPipe(SaveBitmapCommand.class)
 .createObservableResult(new SaveBitmapCommand(bitmap))
 .map(SaveBitmapCommand::getResult) .flatMap((file) -> janet.createPipe(UploadHttpAction.class) .createObservableResult(new UploadHttpAction(file)) .map(UploadHttpAction::getResponse) )
 .subscribe(callback::onSuccess, callback::onFail);
 }
 }
  26. Janet Samples: • Simple Android app: https://github.com/techery/janet-http-android-sample • Advanced Android

    app: https://github.com/techery/janet-architecture-sample • Flux-like Android app: https://github.com/techery/janet-flux-todo