cerner / beadledom Goto Github PK
View Code? Open in Web Editor NEWA simple, composable framework for building RESTful services
Home Page: http://engineering.cerner.com/beadledom
License: Apache License 2.0
A simple, composable framework for building RESTful services
Home Page: http://engineering.cerner.com/beadledom
License: Apache License 2.0
The current archetype doesn't provide any configuration around the stagemonitor widget [1]. The default configuration should be to have it locked down. Possibly with a comment or content updates in the documentation site on how the configuration could be changed for the widget.
[1] https://github.com/stagemonitor/stagemonitor/wiki/Only-show-the-widget-for-authorized-users
In development environment many developers deploy their own version of services. Adding SCM Revision field will be helpful in identifying which scm version of the service is deployed.
[WARNING] Invalid POM for p6spy:p6spy:jar:2.2.0, transitive dependencies (if any) will not be available, enable debug logging for more details
See issue p6spy/p6spy#300 for more details
We have found that many features that may be used in server side JAX-RS resources don't translate well to being shared between the client and service using a shared JAX-RS resource interface.
PATCH
will be used very different on the client side compared to how it will be consumed on the server side.
Server sides with many different query parameters that can be used in different combinations also don't make for a very pleasant API client side and adding query parameters to an existing endpoint would be a non-passive change for the client side when using a shared interface.
Sharing JSON models is still useful, but the JAX-RS resources should be separated between the client and server.
In a conversation with @johnlcox we have discussed a possibility of adding a common offset based pagination component to the beadledom. The component would decorate a list of objects returned from target methods with all necessary pagination elements.
Using John’s implementation of pagination as part of one of the Cerner projects as a model the component would include:
I'm opening this issue to discuss design of the solution and feedback would be appreciated.
As of beadledom 2.7, the WebApplicationExceptionMapper class constructs the exception message based on the status code's reasonPhrase. I was wondering if this can be enhanced to support custom exception messages too. That way the consumer can get a better understanding of why that error was thrown instead of receiving the default reason phrase.
BeadledomClientModule
This causes these kinds of conflicts,
1) A binding to com.google.common.base.Optional<com.google.inject.Provider<java.lang.String>> annotated with interface com.cerner.beadledom.client.CorrelationIdClientHeader was already configured at com.cerner.beadledom.client.BeadledomClientModule.configure(BeadledomClientModule.java:67) (via modules: com.cerner.pophealth.datamgmt.template.service.ConflictExample$MyPublicModule -> com.cerner.beadledom.client.BeadledomClientModule -> com.google.inject.multibindings.OptionalBinder$RealOptionalBinder).
at com.cerner.beadledom.client.BeadledomClientModule.configure(BeadledomClientModule.java:67) (via modules: com.cerner.pophealth.datamgmt.template.service.ConflictExample$MyPrivateModule -> com.cerner.beadledom.client.BeadledomClientModule -> com.google.inject.multibindings.OptionalBinder$RealOptionalBinder)
Here is an example in a unit test that demonstrates this behavior.
From my sparse understanding of Guice, it seems like Guice doesn't like that the public/root environment has a definition of this correlation header and the private module/space has its own definition, with the private module having both definitions available to it.
This is interesting because I would have imagined this shouldn't happen since everything is namespaced by the annotation used. This is true for all/most things in BeadledomClientModule
except the correlation header since this is actually a String
with the annotation CorrelationIdClientHeader
.
Worth noting you don't have any problem if both of these clients are in the same public module or public guice space/environment.
I understand this setup is a bit particular but thought it was worth mentioning.
beadledom-client-jackson should provide a way to provide filter providers to the Jackson ObjectMapper [1]. Allowing this would allow clients to filter when serializing objects to JSON.
[1] https://www.baeldung.com/jackson-serialize-field-custom-criteria
It would be nice if beadledom-client exposes a HealthResource by default so that the consumers of that client can use it to check the health of their dependencies. Currently all the clients write a HealthResource themselves and expose it which I think can be avoided if beadledom provides it off the shelf.
We need to have swagger UI support https protocol.
guava and javax dependencies added to the client, service and api modules of the archetype, without specifying the version in parent pom is causing the build of the generated service to fail.
commit: 158568e
The initialization of Resteasy resources using the ModuleProcessor
in ResteasyContextListener
could cause an infinite loop if the Guice Injector has parent injectors.
The fix is to reassign the parent injector back to the injector
variable instead of creating a parent
variable. Testing is the harder bit. Before fixing this we should identify a way to test the current behavior and write a failing test.
We have API models that use OffsetDataTime fields. These are being serialized from the service side to strings as expected
...
"creationDate": "2017-09-06T19:21:15Z",
"lastModifiedDate": "2017-09-06T19:21:15Z"
...
however, when using the client to try to deserialize them we're seeing
javax.ws.rs.ProcessingException: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of java.time.OffsetDateTime
(no Creators, like default construct, exist): no String-argument constructor/factory method to deserialize from String value ('2017-09-06T20:49:14.584Z')
I've tried following this documentation: http://engineering.cerner.com/beadledom/2.5.1/docs/manual/jackson.html to add com.fasterxml.jackson.datatype.jsr310.JavaTimeModule to try and add in the proper deseriaizer but it didn't work. I'm a guice noob so maybe I'm doing something wrong? Is there more explicit documentation on how to do this? Or is there something else I'm doing wrong?
Our services currently return responses with 40x-50x if we encounter any client-side/server-side errors. The response now contains a plain text message explaining what's bad in the request and wraps it in to a Jax-rs response object. For example: all 400 responses use the JaxRsParamCondition method provided by beadledom.
It would be helpful to have a more structured error message Google API as an example passed to the service consumer which they can parse and take necessary actions.
beadledom-json-common wasn't added to the BOM, so it needs to be explicitly declared in a parent POM if used in child modules. beadledom-json-common
should be added to the BOM to reduce the amount of dependencies that need to be explicitly declared.
com.cerner.beadledom:resteasy-client has a dependency on org.jboss.resteasy:resteasy-client:jar:3.0.19.Final which in compiled to Java 7. This makes using the Beadledom from the client side also require Java 7. If org.jboss.resteasy:resteasy-client is downgraded to 3.0.12.Final, then it should be compatible with Java 6.
I was getting the following exception when running Beadledom client side code on Java 6
Exception in thread "main" java.lang.UnsupportedClassVersionError: org/jboss/resteasy/client/jaxrs/ClientHttpEngine : Unsupported major.minor version 51.0
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClassCond(ClassLoader.java:637)
at java.lang.ClassLoader.defineClass(ClassLoader.java:621)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
at com.cerner.beadledom.client.resteasy.ResteasyClientBuilderFactory.create(ResteasyClientBuilderFactory.java:15)
at com.cerner.beadledom.client.resteasy.ResteasyClientBuilderFactory.create(ResteasyClientBuilderFactory.java:11)
at com.cerner.beadledom.client.BeadledomClientBuilderProvider.get(BeadledomClientBuilderProvider.java:63)
at com.cerner.beadledom.client.BeadledomClientBuilderProvider.get(BeadledomClientBuilderProvider.java:28)
at com.google.inject.internal.ProviderInternalFactory.provision(ProviderInternalFactory.java:81)
at com.google.inject.internal.InternalFactoryToInitializableAdapter.provision(InternalFactoryToInitializableAdapter.java:53)
at com.google.inject.internal.ProviderInternalFactory$1.call(ProviderInternalFactory.java:65)
at com.google.inject.internal.ProvisionListenerStackCallback$Provision.provision(ProvisionListenerStackCallback.java:115)
at com.cerner.beadledom.lifecycle.legacy.LifecycleProvisionListener.onProvision(LifecycleProvisionListener.java:49)
at com.google.inject.internal.ProvisionListenerStackCallback$Provision.provision(ProvisionListenerStackCallback.java:126)
at com.google.inject.internal.ProvisionListenerStackCallback.provision(ProvisionListenerStackCallback.java:68)
at com.google.inject.internal.ProviderInternalFactory.circularGet(ProviderInternalFactory.java:63)
at com.google.inject.internal.InternalFactoryToInitializableAdapter.get(InternalFactoryToInitializableAdapter.java:45)
at com.google.inject.internal.InjectorImpl$2$1.call(InjectorImpl.java:1016)
at com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1103)
at com.google.inject.internal.InjectorImpl$2.get(InjectorImpl.java:1012)
at com.google.inject.internal.InjectorImpl.getInstance(InjectorImpl.java:1047)
at com.cerner.beadledom.guice.dynamicbindings.DynamicBindingProviderImpl.get(DynamicBindingProviderImpl.java:34)
at com.cerner.beadledom.client.BeadledomClientProvider.get(BeadledomClientProvider.java:31)
at com.cerner.beadledom.client.BeadledomClientProvider.get(BeadledomClientProvider.java:16)
at com.google.inject.internal.ProviderInternalFactory.provision(ProviderInternalFactory.java:81)
at com.google.inject.internal.InternalFactoryToInitializableAdapter.provision(InternalFactoryToInitializableAdapter.java:53)
at com.google.inject.internal.ProviderInternalFactory$1.call(ProviderInternalFactory.java:65)
at com.google.inject.internal.ProvisionListenerStackCallback$Provision.provision(ProvisionListenerStackCallback.java:115)
at com.cerner.beadledom.lifecycle.legacy.LifecycleProvisionListener.onProvision(LifecycleProvisionListener.java:49)
at com.google.inject.internal.ProvisionListenerStackCallback$Provision.provision(ProvisionListenerStackCallback.java:126)
at com.google.inject.internal.ProvisionListenerStackCallback.provision(ProvisionListenerStackCallback.java:68)
at com.google.inject.internal.ProviderInternalFactory.circularGet(ProviderInternalFactory.java:63)
at com.google.inject.internal.InternalFactoryToInitializableAdapter.get(InternalFactoryToInitializableAdapter.java:45)
at com.google.inject.internal.ProviderToInternalFactoryAdapter$1.call(ProviderToInternalFactoryAdapter.java:46)
at com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1103)
at com.google.inject.internal.ProviderToInternalFactoryAdapter.get(ProviderToInternalFactoryAdapter.java:40)
at com.google.inject.internal.SingletonScope$1.get(SingletonScope.java:145)
at com.google.inject.internal.InternalFactoryToProviderAdapter.get(InternalFactoryToProviderAdapter.java:41)
at com.google.inject.internal.InternalInjectorCreator$1.call(InternalInjectorCreator.java:205)
at com.google.inject.internal.InternalInjectorCreator$1.call(InternalInjectorCreator.java:199)
at com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1092)
at com.google.inject.internal.InternalInjectorCreator.loadEagerSingletons(InternalInjectorCreator.java:199)
at com.google.inject.internal.InternalInjectorCreator.injectDynamically(InternalInjectorCreator.java:180)
at com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:110)
at com.google.inject.Guice.createInjector(Guice.java:96)
at com.google.inject.Guice.createInjector(Guice.java:73)
at com.cerner.beadledom.lifecycle.legacy.BeadledomLifecycleInjectorBuilder.createInjector(BeadledomLifecycleInjectorBuilder.java:59)
at com.cerner.beadledom.lifecycle.GuiceLifecycleContainers.initialize(GuiceLifecycleContainers.java:42)
at com.cerner.beadledom.kepler.commands.BeadledomKeplerCommand.run(BeadledomKeplerCommand.java:64)
at com.cerner.kepler.commands.Command.run(Command.java:130)
at org.apache.hadoop.util.ToolRunner.run(ToolRunner.java:70)
at com.cerner.kepler.commands.BasicCommandRunner.run(BasicCommandRunner.java:74)
at com.cerner.kepler.commands.BasicCommandRunner.run(BasicCommandRunner.java:44)
at com.cerner.pophealth.appservices.migration.command.Main.main(Main.java:27)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.apache.hadoop.util.RunJar.main(RunJar.java:208)
Add Gitter support to facilitate discussion.
Configuration of the number of retries that are tried should be configurable. Currently it is hard coded to 3.
The current recommended way from beadledom to create the client module is to expose the client resource classes. If you consume another client module like this in your Beadledom service without wrapping it in a PrivateModule
to ensure the resource classes are not exposed they will end up being served by Resteasy/JAXRS.
Wrapping these client modules in a PrivateModule
is fairly trivial but not intuitive. Its easy to forget the magic that is JAXRS which will interrogate Guice's environment for any JAXRS providers it finds. We ran into this and only discovered this issue after looking at our swagger doc and noticing endpoints that were not ours.
I've seen others create a POJO client that takes in the BeadledomClient
and provides get methods for all the resource classes. Then they only expose this POJO client instead of the resource classes themselves.
This is mostly to start discussion on this to see if theres a fix worth doing here or if service developers just need to understand this.
java.lang.ArithmeticException: / by zero
at com.cerner.beadledom.pagination.OffsetPaginationLinks.lastLink(OffsetPaginationLinks.java:86) ~[beadledom-pagination-3.1.jar:na]
at com.cerner.beadledom.pagination.OffsetPaginatedListLinksWriterInterceptor.aroundWriteTo(OffsetPaginatedListLinksWriterInterceptor.java:39) ~[beadledom-pagination-3.1.jar:na]
at org.jboss.resteasy.core.interception.AbstractWriterInterceptorContext.proceed(AbstractWriterInterceptorContext.java:129) ~[resteasy-jaxrs-3.6.1.Final.jar:3.6.1.Final]
at org.jboss.resteasy.plugins.interceptors.encoding.GZIPEncodingInterceptor.aroundWriteTo(GZIPEncodingInterceptor.java:92) ~[resteasy-jaxrs-3.6.1.Final.jar:3.6.1.Final]
at org.jboss.resteasy.core.interception.AbstractWriterInterceptorContext.proceed(AbstractWriterInterceptorContext.java:129) ~[resteasy-jaxrs-3.6.1.Final.jar:3.6.1.Final]
at org.jboss.resteasy.plugins.interceptors.encoding.ServerContentEncodingAnnotationFilter.aroundWriteTo(ServerContentEncodingAnnotationFilter.java:60) ~[resteasy-jaxrs-3.6.1.Final.jar:3.6.1.Final]
JAX-RS does not have a PATCH annotation for supporting HTTP patch method like it does for others like GET or POST. We should add a PATCH annotation that can be used for handling the patch HTTP method.
As a consumer it would be helpful if Beadledom provide a BOM which provided the full list of dependencies so I don't have to include every beadledom library in my pom
Beadledom currently uses Tomcat for running the service locally and provides no docker functionality at all. This means there is a good amount of changes and boilerplate setup still required after creating a Beadledom project.
This is an enhancement request to switch to Jetty and add docker.
There are some legality issues with consuming jsr305 as it's in conflict with Oracle java binary license.
F. JAVA TECHNOLOGY RESTRICTIONS. You may not create, modify, or change the behavior of, or authorize your licensees to create, modify, or change the behavior of, classes, interfaces, or subpackages that are in any way identified as "java", "javax", "sun", “oracle” or similar convention as specified by Oracle in any naming convention designation.
It looks like a majority of our dependencies are moving away from jsr305, which
We'll have to wait for gauva to release new package that does not consume jsr305, google/guava#2960; as well as Spotbug, spotbugs/spotbugs#180
This is more or less to start a discussion on migrating away from jsr305 and prepping for this migration.
We have a use case where we want to ignore certain transactions from being sent to New Relic. New Relic support several ways to ignore a transaction. Since not all consumer will have access to the modules they are installing, having a request filter to ignore requests that match a URI pattern seems like a good approach.
If the request filter is the desired approach, how should the consumers configure the paths they want excluded? Some options are an Ant path pattern used within Maven and Stagemonitor. Another option would be Java regex pattern.
To exclude anything under the /meta/
path. The Ant pattern would be /meta/**
while the regex would be ^/meta//?.*
. Thoughts?
In order to do things like caching of query/insertion results deep within a chain of nested transactions, it would be helpful to have some sort of callback mechanic that would trigger when the root/top-level transaction is committed.
Some possible classes that could be created to help facilitate this:
TransactionCommitHooks
- Could have a register method that nested transactional code could register lambda expressions with to be executed when the top-level commit is completed. Probably would use ThreadLocal
for this since ThreadLocal
is already used for DslContextProviderImpl
.DslContextProviderImpl
to call the proper set of registered hooks when the unit of work is ended.Should the hooks fire for rollback? Maybe the lambda takes a bool parameter that says whether it was successful or not? Alternatively, maybe there is a separate success/failure callback? What about nested savepoint commit callbacks? Is there value to having callbacks for all the different levels and some sort of indicator whether it's nested or root?
References:
django uses a callback function to handle performing tasks after commit, https://docs.djangoproject.com/en/2.1/topics/db/transactions/#performing-actions-after-commit
If a HealthStatus
includes an exception it will not be logged out with the error message in HealthChecker
. The issue is here. On this line it includes status.getException()
at the end of the error method, but thats actually an Optional< Throwable>
, so it is not being included since its not a Throwable
.
The metadata(...)
methods in OffsetPaginatedList seem clunky to use in consumers. The three OffsetPaginatedList#metadata
methods should be deprecated. The metadata builder should be the preferred way of building metadata if you need it, instead of calling overloaded methods. The OffsetPaginatedList#metadata(Long)
can documented to prefer a new OffsetPaginatedList#totalResults(Long)
and the other two can be documented to just use the metadata builder.
The default HttpClient retry behavior is to only retry on 503s. We override that to be any 500s which is kind of weird and possibly dangerous when talking about PUT, POST, DELETE methods. We may want to reconsider this and only retry on 503's or at least only for GETS if we do 500s.
Device: Macbook Pro
OS version: High Sierra 10.13.3
Java SDK version: 1.8.0_192-b12
Beadledom version: 3.2.5
Currently when using the ResteasyModule alongside other modules for Beadledom Service Clients, the installation order of the modules will determine which ObjectReader is used to deserialize a request body. I believe that this is caused by the way that the ResteasyProviderFactory is generated within ResteasyContextListener. This will simply iterate through all of the bindings from our injector and then add all of the relevant/available providers to the ResteasyProviderFactory for use later. This causes issues when you are using the AnnotatedJacksonModule since it will lead to there being multiple providers bound within the injector. Resteasy will then simply use the first one that was bound during injection to handle requests. This can lead to ObjectMappers that were annotated to only be used within a service client to be used for the entire service instead.
{}
The documentation needs to be updated to include that a personal access token needs to be required for releasing since we require 2 factor authentication.
https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/
JAX-RS includes a MediaType
class which has constants for most of the common HTTP media types.
With the addition of the @PATCH
method annotation it may be desired to start using either of the two common JSON PATCH media types for implementations.
In my mind there are two options.
AdditionalMediaTypes
with constants like APPLICATION_MERGE_PATCH_JSON
and APPLICATION_JSON_PATCH_JSON
.PatchMediaTypes
with constants named similarly to above.Another question we should think about is how will we decide which media types to include in beadledom?
On the first time a request is made to the Swagger module it prints an error message directly to the console (not through log4j): UNKNOWN TYPE: com.cerner.beadledom.jaxrs.GenericResponse
. This does not reoccur on subsequent requests and does not seem to have any effect on the functionality of Swagger.
When using GenericResponse with the beandledom-jaxrs-clientproxy project, and the resource interface returns a GenericResponse without a type, the client call fails with an exception because the code expects the response to have a entity.
GenericResponse getGenericWithNoType();
java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
at com.cerner.beadledom.client.proxy.GenericClientProxy.buildGenericResponse(GenericClientProxy.java:56)
at com.cerner.beadledom.client.proxy.GenericClientProxy.invoke(GenericClientProxy.java:42)
body()
I had a conversation with @johnlcox regarding possibly adding an exception mapper to Beadledom that will handle all uncaught exceptions in a service, map them, and return an error response in JSON format. This is similar to #24, except that this issue is more about the exception mapper, which would utilize the format discussed in that issue.
The reason this came up was because all of our services which do not handle exceptions as they're thrown are sending responses in plain text with stack traces, and we don't have any real control over what gets sent back.
John proposed the exception mapper would be a class the implements ExceptionMapper<Throwable>
. An example of this is outlined in this article: http://www.codingpedia.org/ama/error-handling-in-rest-api-with-jersey/#22_Uncheckedtechnical_exceptions.
This would hopefully be a first step towards unifying our response structure across application services by implementing the catch all exception mapper with the proper JSON response. I'm opening this issue to discuss design/format/structure of the response, and any feedback is welcomed.
With the redirects that happen for the doc site now the install.sh
script fails when it tries to download beadledom
.
Currently we aren't using the -L
option when executing curl [1] which would allow curl to follow the redirects. We should use that.
We do have a restriction on the response code [2] between 200-299
. I'm not 100% but I think that range will still be fine if the -L
option is provided to curl.
[1] https://github.com/cerner/beadledom/blob/master/archetype/bootstrap/bin/install.sh#L57]
[2] https://github.com/cerner/beadledom/blob/master/archetype/bootstrap/bin/install.sh#L65
When attempting to use AvailabilityResource
or HealthResource
with a client it fails with,
com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of com.cerner.beadledom.health.dto.HealthDto, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information
This looks to be because HealthDto
is an AutoValue class and Jackson is missing the connection between HealthDto
and the implementation AutoValue class AutoValue_HealthDto
.
Add integration tests or a script to make sure the service generated by the archetype does not break on changes to beadledom.
Small changes can easily break the archetype and currently, it's very time consuming to test the service in dev: by generating it from the archetype, changing the version to match the dev version and the building it to make sure it runs.
I am trying to consume Beadledom with a different APM than Stagemonitor. Currently Stagemonitor is included in the BeadledomModule [1]. Would you consider a PR that removes Stagemonitor from BeadledomModule, and make consumers add the StagemonitorModule [2] directly if they want to use Stagemonitor? Or maybe create additional modules that would also include StagemonitorModule like the current BeadledomModule.
[1]
https://github.com/cerner/beadledom/blob/master/core/src/main/java/com/cerner/beadledom/core/BeadledomModule.java
[2] https://github.com/cerner/beadledom/blob/master/stagemonitor/src/main/java/com/cerner/beadledom/stagemonitor/StagemonitorModule.java
Currently beadledom uses 1.3.12 which is over 2 years old. They have then re-named themselves (group id) to io.swagger and are up to 1.5.13.
They talk about compatibility but I'm guessing that has to do with the API data vs the class compatibility. Though its only 2 minor version bumps up all the classes seemed to have been re-named to io.swagger.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.