Open foal opened 7 years ago
Hi! yep, I need to support other codecs, to support jackson-gwt autorest need to generate an ObjectMapper, this will make autorest coupled with jackson-gwt, so need to be decoupled. I have various strategies in mind BUT...
For now, autorest works perfect with JsInterop, is super small and almost zero-overhead lib only using annotation processor. But jackson-gwt uses generators so for now if you want to use jackson-gwt just use resty-gwt, both use generators and both works perfectly together. I'll promise than if jackson-gwt start using processor I'll make a release supporting it bc at that point both can be used "theoretically" with j2cl.
Also not that IMO this lib, and more or less resty-gwt can be used only with JAX-RS as the exposed API, if you do that you should change both without pain, so again for now using resty-gwt with jackson-gwt is the best option as using autorest with JsInterop 👍. To do that I strongly recomended to use resty-gwt DirectInterfaces, those are a bit anoying because of the REST.withCallback().call(), and hehe bc if you use it you are coupled all over the place with resty-gwt. To decouple that you can use https://github.com/ibaca/restygwt-rxadapter. I think is broken, I make some changes to make it better but this was the point when I start autorest, if you are going to use we can fix it 🙌.
Hi, I understand your doubts about jackson-gwt and it "old school" code generation.
Regarding JAX-RS, direct interface and decoupling GWT from server side I am using the "double interface" trick: I have base interface (ClientService just a flag interface) in shared project e.g.
public interface AboutService extends ClientService {
String SRV_PATH = "/about/all"; //$NON-NLS-1$
List<AboutModel> getAll();
}
that has been implemented by Spring REST controller and is completely independent of GWT (no RESTY or JAX-RS dependencies) and the REST interface in gwt project that extends the base interface and adapts it to GWT like below.
public interface AboutRestService extends AboutService, DirectRestService {
@GET
@Path(SRV_ROOT + SRV_PATH)
@Override
List<AboutModel> getAll();
}
Will check your adapter.
And with AutoREST did you use just one common JAX-RS interface? or did you need to do the 2-interfaces trick?
I have obtained pretty great results with JsInterop, maybe if you ask about what you miss from Jackson or about JsInterop friendly alternatives you might find that Jackson is not so needed.
Hmm... I am pretty new with RxJava and never use it before :) So the progress is not very fast. I take the small project with GWT and Resty and try to replace the Resty with AutoRest. At least one call. So the
public interface LogUiLoggerService extends RestService {
@GET
@Path("/rest/loggers/{inherited}/{filter}")
void get(@PathParam("filter") String filter, @PathParam("inherited") boolean inherited, MethodCallback<List<LogUiLogger>> callback);
@POST
@Path("/rest/logger")
void set(LogUiLogger loger, MethodCallback<Boolean> callback);
@GET
@Path("/rest/suggestLogger/{query}/{limit}")
void requestSuggestion(@PathParam("query") String query, @PathParam("limit") int limit, MethodCallback<List<String>> callback);
}
was duplicate with
@AutoRestGwt
@Path("/rest")
public interface LogUiLoggers {
@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Object")
public static class Logger {
public String name;
public String level;
public String effectiveLevel;
public Appender[] appenders;
public boolean additive;
@JsOverlay
public final LogUiLogger to() {
final LogUiLogger result = new LogUiLogger();
result.setAdditive(additive);
result.setAppenders(Stream.of(appenders).map(Appender::to).collect(Collectors.toList()));
result.setEffectiveLevel(LogUiLevel.valueOf(effectiveLevel));
result.setLevel(level == null ? null : LogUiLevel.valueOf(level));
result.setName(name);
return result;
}
}
@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Object")
public static class Appender {
public String name;
public String className;
@JsOverlay
public final LogUiAppender to() {
final LogUiAppender result = new LogUiAppender();
result.setClassName(className);
result.setName(name);
return result;
}
}
@GET
@Path("/loggers/{inherited}/{filter}")
Observable<Logger> get(@PathParam("filter") String filter, @PathParam("inherited") boolean inherited);
@POST
@Path("/logger")
Single<Boolean> set(LogUiLogger loger);
@GET
@Path("/suggestLogger/{query}/{limit}")
Observable<String> requestSuggestion(@PathParam("query") String query, @PathParam("limit") int limit);
}
and
srv.get(getFilter(), getInherited(), new AbstractMethodCallback<List<LogUiLogger>>(bus) {
@Override
public void onSuccess(final Method method, final List<LogUiLogger> result) {
dataGrid.setRowCount(result.size());
updateRowData(start, result.subList(start, result.size()));
}
});
was replaced with
final Observable<Logger> ls = logUiLoggers.get(getFilter(), getInherited());
ls.count().subscribe(dataGrid::setRowCount);
ls.skip(start).map(Logger::to).toList().subscribe(list -> updateRowData(start, list));
I will need to create special objects Logger
and Appender
to reflect the output of JSON.parse and add mappings methods ("to") to convert the JS objects to the domain objects. Maybe it is Ok with 2-3 objects but much annoing with tens of objects in the domain model.
Yep, this is how AutoREST best works right now. Duplicating the DTO and a domain object is annoying, so you need to try to use the DTO, but also be careful to not to influence the DTO with logic, if you need inheritance and logic then this is the moment when you should stop using the DTO. It is a different approach and it requires various refactors in your code so I also recommend making the migration progressively, one service interface each time. Even if you get forced to have a DTO object and a Domain object this can be an advantage too bc sometimes you need something specific in the domain, may be motivated for performance or to easier UI handling reasons, that might change your API, so if there are 2 models, the API end up more... API friendly and the domain fits perfectly to be used in the widgets. But to be honest, in our project, we most of the time reuse the DTOs and usually end up in the table, chart or form directly. Sometimes we just wrap the DTO in a decorator/adaptor class to add more data or just to adapt to some other type, but we do not have many duplicated models in the client. This kind of models fits perfectly with D3 and similar libs.
About RxJava, you should be careful, RX is usually lazy and reusable (not always true! it just an option, so each place that returns an RX type, like an Observable, should specify what it is going to happen). But in case of AutoREST it is lazy and reusable which means that srv.get(…) returns an Observable
final Observable<Logger> ls = logUiLoggers.get(getFilter(), getInherited()).cache();
ls.count().subscribe(dataGrid::setRowCount);
ls.skip(start).map(Logger::to).toList().subscribe(list -> updateRowData(start, list));
The cache will subscribe only one time to the actual request and will remember the result so the 2 subscribe call will obtain the same result making only one server request.
Ok. Thank you for your suggestions regarding RX. I will update the code with cache() method :)
But the main issue is the conversation - for such example it is easy, but even this example contains 2 string to enum and one array to list conversation, second point the getters/setters - I miss them :)
In other projects, I use the DTOs but they are significant complex (in details) than simple data structure with public fields. I will check the restygwt-rxadapter for now. Hope @nmorel will migrate the project to the APT soon :) (https://github.com/nmorel/gwt-jackson/issues/63)
One more question. I see the rxjava-gwt project is migrating (migrated?) to RxJava2, but AutoREST uses the previous version of rxjava-gwt based on RxJava1. What is the status?
I have already migrated all tools (rxjava-gwt, rxgwt and autorest) to RxJava 2 the problem is that gwt uses eclipse jdt to compile and it contains a bug that make RxJava 2 compilation super slow, so for now you need to use a custom build (more info in the rxjava-gwt project). Gwt just need to upgrade to the last Jdt, And this is almost mandatory to add Java9 Lang features so y hope this will occur in gwt 2.9. The custom build only fixes that so IMO should not be a problem. Anyway, yep using RxJava 2 is a good idea!
Hi Just wanted to add my 2 cents. In general RxJava and Autorest are totally cool - I love it! But the JsInterop JSON serializing/deserializing really is too simple - there is no way to hook in and do some simple conversion, like for example handling of enums. I don't want to go with resty-gwt and jackson-gwt, its a mess compared to the simplicity of autorest. I long for a JSON serializing/deserializing with just some form of hook points or adaptability. I live with it now, mainly because it is so simple to string in some mapping in an observable chain, but it puts some serializing/deserializing logic where it should not be. --Jesper
Yep, hehe we feel that way too, but any solution will require a parallel instance tree, I mean, first parsing as JSON (which is the first and unique step right now) and second instantiating new types (so no JsType native) and setting the actual values. And I still think it doesn't worth.
What we do is to add the "parsing" method just next to the native field, like...
@JsType(native = true, package = GLOBAL, name = "Object") class SomeDto {
public int type;
@JsOverlay @JsonIgnore
public final SomeEnum getTypeEnum() { return SomeEnum.values()[type]; }
}
We add overlay for setter and getters when the type dosen match, and for array, usually T[] returned as Stream
yeah, I have an annotation processor that generates methods like that in an interface, I can then just add the interface to the class. But still.... Trust that you are on the lookout for some neat solution ;-)
For enums we do something similar, but we use the string representation instead:
Let's assume we have enum:
public enum Animals {
Dog,
Cat
}
@JsProperty(name = "Animal")
public native String getJsAnimal();
@JsProperty(name = "Animal")
public native void setJsAnimal(String animal);
@JsOverlay
public final Animal getAnimal() {
return Animal.valueOf(getJsAnimal());
}
@JsOverlay
public final void setAnimal(Animal animal) {
setJsAnimal(animal.name);
}
This is a very verbose representation of what we do; our models are generated to automatically create the @JsProperty
and @JsOverlay
methods with some sanity/null checks.
We think we managed to do what was originally requested in this thread (seamless integration of autorest with gwt-jackson-apt) in this pull request: #11 .
I won't repeat our approach here - it is all described in the comments of #11. The one thing I'll emphasize again is only that our approach is keeping autorest completely unaware of gwt-jackson-apt (and the other way around of course). It is as minimal as it gets, while still allowing to use the two libs together. ... or even use autorest with whatever serialization/deserialization policy you have in mind - including just continue using straight JSON-compatible @JsType(isNative=true)
pseudoclasses.
@ivmarkov have you checked this https://github.com/DominoKit/domino-autorest-jackson/blob/master/README.md
@vegegoku
Unless we are reading your code incorrectly, domino-autorest-jackson does not really support deserialization of the following:
Observable<Collection<MyBean>>
Observable<List<MyBean<MyParameter>>
Basically, you only support either non-parameterized Java Beans or Java arrays of non-parameterized Java Bean as a return type of each method, i.e. Observable<MyBean[]>
.
Similarly for input parameters, you only allow either non-parameterized Java Beans, or arrays of Java Beans, e.g.:
MyBean
MyBean[]
But not really:
MyBean<InstantiatedWithMyCustomClass>
List<MyBean>
... and that's fair, because autorest currently only provides a Class
-style type information - as a parameter of the the RequestBuilder::as
method, and - as you did with a little bit of reverse engineering - by introspecting the getClass()
method of the data
parameter which is passed to each RequestBuilder::param
method.
The thing is, without extended type information, and in the presence of Java erasure you can only get that far - no Java collections, and in general - no instantiated parameterized Java beans. but we need those - especially the collections. Hence why we introduced the Type
class in the pull request I'm referring to - because it is recording the full parameterized type information, which is passed as an additional parameter to each RequestBuilder::param
method (and to RequestBuilder::as
). This way you get full type info that you can use as you want - for example, to instantiate with it the correct ObjectMapper
from gwt-jackson-apt.
One more thing: gwt-jackson-apt currently has restrictions which e.g. prohibit it from generating Object Mappers for generified (but instantiated) types. I.e. it currently cannot generate ObjectMapper
implementations for the following types:
List<MyBean>
MyBean<MyInstantiatedType>
List<List<MyBean>>
It will therefore probably not be a surprise that we have also extended gwt-jackson-apt so that it can generate ObjectMapper implementations for all of the above types, and more. The pull request will land to your code repo in a few hours.
Thoughts/feedback greatly appreciated!
@ivmarkov such PR is highly appreciated. thank you
Native JSON deserialization is quite fast, but have some limitation. I need to have the same domain model on both sides (browser and server) and Jackson is the more flexible solution.