Share
Embed
Transcript
47 slides
-
What’s our application made of? User Interface UI Presentation Service
Presentation Entities Service -
Usual Service Layer • Communication with server side • Processing
business layer • Working with entities (storage) • Security rules • Other use cases -
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 -
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 + -
Usual Service Layer • Communication with server side • Processing
business layer • Working with entities (storage) • Security rules • Other use cases } Actions -
When we think about actions NetworkManager UserManager StorageManager LoginAction RegisterAction
UsersAction AddFriendAction DeleteFriendAction StoreFriendsAction -
Problem of different approaches • async-http • Goro • EventBus
• DBFlow • Retrofit 2 • Goro • RxJava • Realm • Retrofit • RoboSpice • RxJava • SnappyDB -
Problem of different approaches • Action-based Framework • Action-based Framework
• Action-based Framework -
Based on actions Flexibility and scalability. Scale functionality using
services Reactive approach for actions interaction by RXJava Throw-safety architecture We made Janet -
Janet Flow Result Action Send ActionPipe Subscribe Janet Service Result
Result PROGRESS SUCCESS FAIL START -
Janet Flow Result Action Send ActionPipe Subscribe Janet Service Result
Result PROGRESS SUCCESS FAIL START Result = Action + Status -
Janet Flow ActionState Action Send ActionPipe Subscribe Janet Services ActionState
ActionState PROGRESS SUCCESS FAIL START ActionState = Action + Status -
Services Janet janet = new Janet.Builder() .addService(new HttpActionService(API_URL, httpClient, converter))
.addService(new SqlActionService(...)) .addService(new XyzActionService(...)) .build(); -
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; -
Possible Services LocationActionService PersistActionService SSHActionService BitmapActionService FTPActionService TrackingActionService SyncActionService GITActionService
RESTActionService VideoActionService SQLActionService -
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 -
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 -
How to use? // Create pipe for action class with
janet ActionPipe<SampleHttpAction> actionPipe = janet.createPipe(SampleHttpAction.class); -
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) -> {}) ); -
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(); -
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(); -
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(); -
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()); } -
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)); } -
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. -
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; } -
Command • Custom operations • Business use cases • Janet
action chains Processed by CommandActionService See https://github.com/techery/janet-command -
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(); } } -
Command ActionPipe<SaveBitmapCommand> actionPipe = janet.createPipe(SaveBitmapCommand.class); actionPipe.createObservable(new SaveBitmapCommand(bitmap)) .subscribe(new ActionStateSubscriber<SaveBitmapCommand>() .onSuccess(command
-> { File file = command.getResult(); doSometing(file); }) .onFail((command, throwable) -> showError(throwable))); -
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); } } -
Janet https://github.com/techery/janet Contributions are welcome Version: Used in 4 project
in 3 different componies Tested on production -
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