Code Monkey home page Code Monkey logo

ronenhamias / scalecube-services Goto Github PK

View Code? Open in Web Editor NEW
26.0 11.0 7.0 3.13 MB

v2.0 - ScaleCube Services provides a low latency Reactive Microservices library for serverless service registry and discovery based on gossip protocol and without single point-of-failure or bottlenecks.

Home Page: http://scalecube.io/

License: Apache License 2.0

Java 98.66% Shell 1.34%
rsocket reactor3 reactive-streams netty4 reactive flux mono reactor ipc tcp

scalecube-services's Introduction

scalecube-services

Build Status Codacy Badge Maven Central

MICROSERVICES 2.0

An open-source project that is focused on streamlining reactive-programming of Microservices Reactive-systems that scale, built by developers for developers.

ScaleCube Services provides a low latency Reactive Microservices library for peer-to-peer service registry and discovery based on gossip protocol, without single point-of-failure or bottlenecks.

Scalecube more gracefully address the cross cutting concernes of distributed microservices architecture.

ScaleCube Services Features:
  • Provision and interconnect microservices as a service-mesh (cluster)
  • Reactive Streams support.
    • Fire And Forget - Send and not wait for a reply
    • Request Response - Send single request and expect single reply
    • Request Stream - Send single request and expect stream of responses.
    • Request bidirectional - send stream of requests and expect stream of responses.
  • No single-point-of-failure or single-point-of-bottleneck
  • Cluster aware and distributed
  • Modular, flexible deployment models and topology
  • Zero configuration, automatic peer-to-peer service discovery using gossip
  • Simple non-blocking, asynchronous programming model
  • Resilient due to failure detection, fault tolerance, and elasticity
  • Routing and balancing strategies for both stateless and stateful services
  • Low latency and high throughput
  • Takes advantage of the JVM and scales over available cores
  • Embeddable to existing Java applications
  • Natural Circuit-Breaker due to tight integration with scalecube-cluster failure detector.
  • Support Service instance tagging.
  • pluggable service transport
  • pluggable encoders

User Guide:

Basic Usage:

The example provisions 2 cluster nodes and making a remote interaction.

  1. seed is a member node and provision no services of its own.
  2. then microservices variable is a member that joins seed member and provision GreetingService instance.
  3. finally from seed node - create a proxy by the GreetingService api and send a greeting request.
    //1. ScaleCube Node node with no members
    Microservices seed = Microservices.builder().startAwait();

    //2. Construct a ScaleCube node which joins the cluster hosting the Greeting Service
    Microservices microservices = Microservices.builder()
        .seeds(seed.cluster().address()) // some address so its possible to join the cluster.
        .services(new GreetingServiceImpl())
        .startAwait();


    //3. Create service proxy
    GreetingsService service = seed.call().create().api(GreetingsService.class);

    // Execute the services and subscribe to service events
    service.sayHello("joe").subscribe(consumer -> {
      System.out.println(consumer.message());
    });

Basic Service Example:

  • RequestOne: Send single request and expect single reply
  • RequestStream: Send single request and expect stream of responses.
  • RequestBidirectional: send stream of requests and expect stream of responses.

A service is nothing but an interface declaring what methods we wish to provision at our cluster.

@Service
public interface ExampleService {

  @ServiceMethod
  Mono<String> sayHello(String request);

  @ServiceMethod
  Flux<MyResponse> helloStream();
  
  @ServiceMethod
  Flux<MyResponse> helloBidirectional(Flux<MyRequest> requests);
}

Maven

With scalecube-services you may plug-and-play alternative providers for Transport,Codecs and discovery. Scalecube is using ServiceLoader to load providers from class path,

You can think about scalecube as slf4j for microservices - Currently supported SPIs:

Transport providers:

  • rsocket-services-transport: using rsocket to communicate with remote services.

Message codec providers:

Service discovery providers:

Binaries and dependency information for Maven can be found at http://search.maven.org.

https://mvnrepository.com/artifact/io.scalecube

Maven Central

To add a dependency on ScaleCube Services using Maven, use the following:

 <!-- -------------------------------------------
   scalecube core and api:   
 ------------------------------------------- -->

 <!-- scalecube apis   -->
 <dependency>
  <groupId>io.scalecube</groupId>
  <artifactId>scalecube-services-api</artifactId>
  <version>2.x.x</version>
 </dependency>
 
 <!-- scalecube services module   -->
 <dependency>
  <groupId>io.scalecube</groupId>
  <artifactId>scalecube-services</artifactId>
  <version>2.x.x</version>
 </dependency>
 

 <!--

     Plugins / SPIs: bellow a list of providers you may choose from. to constract your own configuration:
     you are welcome to build/contribute your own plugins please consider the existing ones as example.

  -->

 <!-- -------------------------------------------
   scalecube transport providers:   
 ------------------------------------------- -->
 <dependency>
  <groupId>io.scalecube</groupId>
  <artifactId>rsocket-services-transport</artifactId>
  <version>2.x.x</version>
 </dependency>
 
 <!-- -------------------------------------------
   scalecube message serialization providers:
   ------------------------------------------- -->

 <!-- jackson scalecube messages codec -->
 <dependency>
  <groupId>io.scalecube</groupId>
  <artifactId>scalecube-services-jackson</artifactId>
  <version>2.x.x</version>
 </dependency>

<!-- protostuff scalecube messages codec -->
 <dependency>
  <groupId>io.scalecube</groupId>
  <artifactId>scalecube-services-protostuff</artifactId>
  <version>2.x.x</version>
 </dependency>

 <!-- -------------------------------------------
    scalecube service discovery provider   
   ------------------------------------------- -->
 <dependency>
  <groupId>io.scalecube</groupId>
  <artifactId>scalecube-services-discovery</artifactId>
  <version>2.x.x</version>
 </dependency>

scalecube-services's People

Contributors

aharonha avatar artem-v avatar codacy-badger avatar dmytro-lazebnyi avatar io-scalecube-ci avatar ronenhamias avatar scooter72 avatar segabriel avatar snripa avatar snyk-bot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

scalecube-services's Issues

Ban method definitions with plain `void` as return type

if (returnType.isAssignableFrom(Void.TYPE)) {
  throw new IllegalMethodDefinitionWeDontSupportThisException();
}

You can define:

  Mono<Void> funcVoidReactive();

You should not be allowed to define:

void funcNonReactive();

Motivation: it's hard to combine plain void functions, let's define convention that our service definitions has to be pure functions, i.e. must return somerhinhg , even if it's Mono. Why do that? For caring about api clients, about their ability to combine.

Dont throw noReachableMemberException

Instead of throwing ServiceUnavailableException return Pubsluher.error()

io.scalecube.services.ServiceCall#noReachableMemberException

  private Address addressLookup(ServiceMessage request) {
    ServiceReference serviceReference =
        router.route(serviceRegistry, request)
            .orElseThrow(() -> noReachableMemberException(request));

    return Address.create(serviceReference.host(), serviceReference.port());
  }

Get rid of publicy avaiable method services() on Microservices class

Method

  public Collection<Object> services() {
    return services.stream().map(ServiceInfo::serviceInstance).collect(Collectors.toList());
  }

is being used only at Reflect's class:

     */
    public Microservices inject() {
      this.inject(this.microservices.services());
      return this.microservices;
    }

Confict with Spring when using @PostConstruct

Background
ServiceA is a scalecube service and Spring bean.

Problem:

  1. If ServiceA contains method annotated with javax.annotation.PostConstruct with no arguments, then this method will be invoked two times - first by Spring, then by io.scalecube.services.Reflect.processPostConstruct.
  2. If ServiceA contains method annotated with javax.annotation.PostConstruct with arguments, then Spring throws java.lang.IllegalStateException: Lifecycle method annotation requires a no-arg method

Possible solution
According to Java API specification method with javax.annotation.PostConstruct annotation MUST NOT have any parameters.
So possible solution is to create in scalecube its own PostConstruct annotation instead of using javax.annotation.

Make clean order in tests

  • Separate between remote and local tests to different suites.
  • Make sure there is no duplication
  • Make sure there is same cases and coverage between local / remote / proxy /dispatchers
  • Make clean order in tests name and cases.
  • Speed up tests setting up cluster once and tear down cluster at the end of scenarios (where possible).

Exceptions thrown during build are spamming the log

RxScheduledExecutorPool

Exception in thread "RxScheduledExecutorPool-1" java.lang.IllegalStateException: Fatal Exception thrown on Scheduler.Worker thread.
at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:59)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask@29c7d154 rejected from java.util.concurrent.ScheduledThreadPoolExecutor@73500f81[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 53]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ScheduledThreadPoolExecutor.delayedExecute(ScheduledThreadPoolExecutor.java:326)
at java.util.concurrent.ScheduledThreadPoolExecutor.schedule(ScheduledThreadPoolExecutor.java:533)
at java.util.concurrent.ScheduledThreadPoolExecutor.execute(ScheduledThreadPoolExecutor.java:622)
at java.util.concurrent.Executors$DelegatedExecutorService.execute(Executors.java:668)
at rx.internal.schedulers.ExecutorScheduler$ExecutorSchedulerWorker.schedule(ExecutorScheduler.java:79)
at rx.internal.schedulers.ExecutorScheduler$ExecutorSchedulerWorker$2.call(ExecutorScheduler.java:145)
at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
... 7 more

ResourceLeakDetector

E 0522-1242:50,065 i.n.u.ResourceLeakDetector LEAK: ByteBuf.release() was not called before it's garbage-collected. See http://netty.io/wiki/reference-counted-objects.html for more information.
Recent access records:
Created at:
io.netty.buffer.PooledByteBufAllocator.newDirectBuffer(PooledByteBufAllocator.java:331)
io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:185)
io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:176)
io.netty.buffer.AbstractByteBufAllocator.ioBuffer(AbstractByteBufAllocator.java:137)
io.netty.channel.DefaultMaxMessagesRecvByteBufAllocator$MaxMessageHandle.allocate(DefaultMaxMessagesRecvByteBufAllocator.java:114)
io.netty.channel.epoll.EpollRecvByteAllocatorHandle.allocate(EpollRecvByteAllocatorHandle.java:71)
io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:793)
io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:404)
io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:304)
io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:884)
java.lang.Thread.run(Thread.java:748) [sc-io-83-3]

PubSub Communication patterns.

Motivation:

lets imagine we have 2 services in our cluster each service might go online and offline at any given point of time.

Service A depends on service B so we want to subscribe to this service when it appears in the cluster and wait for it to stream data to service B

currently this can be done using discovery events from service registry and service discovery.

to reduce the user boiler-plate we can offer an handler to service interface that will delegate events regards service dependencies in the cluster:

Proposed Solution

provide more way for user to react on discovery events by declaring handlers on service interface with regards to the service they want to "trap".

public interface MyService {

   @Discovery(MyService.class)
   void onDiscovery(Flux<DiscoveryEvent> events);

}

public class MyServiceImpl implements MyService, {
  Disposable disposable;
  @Override
  void onDiscovery(Flux<DiscoveryEvent> events){
     events.filter(DiscoveryEvent::isRegistered)
     .map(e -> 
     disposable = event.service().sayHello(someRequest).subscribe()
    ).subscribe()  
  }
}

Alternative approach:

call.discovery(MyService.class).subscribe(e->{
 events.filter(DiscoveryEvent::isRegistered)
     .map(e -> 
     disposable = event.service().sayHello(someRequest).subscribe()
    ).subscribe()  
}).

RSocketServerTransport shutdown improvment

Shutdown process of server transport is as following: ServerTransport.stop() closes server channel and all accepted client channels. While this mechanism serves its purpose this approach is rather exotic. Let's not have exotic code out of nowhere.

In this task:

  • Keep in stop() method just closing of server channel.
  • In order to shutdown entire service transport properly (account that all accepted connections must be closed) -- another system layer must be shutdown - a resources layer.

For reference look at io.scalecube.gateway.rsocket.websocket.RSocketWebsocketServer#stop

Alternative to head and tail request bidirectional

 public Flux<ServiceMessage> requestBidirectional(Publisher<ServiceMessage> publisher, Class<?> responseType) {
    return Flux.create(new Consumer<FluxSink<ServiceMessage>>() {
      @Override
      public void accept(FluxSink<ServiceMessage> sink) {
        Flux.from(publisher).subscribe(request -> {
          Flux<ServiceMessage> responseFlux;
          if (serviceHandlers.contains(request.qualifier())) {
            ServiceMessageHandler serviceHandler = serviceHandlers.get(request.qualifier());
            responseFlux = serviceHandler.invoke(Flux.from(publisher))
                .onErrorMap(ExceptionProcessor::mapException);
          } else {
            responseFlux = transport.create(addressLookup(request))
                .requestBidirectional(Flux.from(publisher))
                .map(message -> dataCodec.decode(message, responseType));
          }
          responseFlux.doOnEach(next->sink.next(next.get()))
          .doOnComplete(()->sink.complete())
          .doOnError(e->sink.error(e))
          .subscribe();
        });
      }
    }, FluxSink.OverflowStrategy.BUFFER);
  }

 private Address addressLookup(ServiceMessage request) {
    ServiceReference serviceReference =
        router.route(serviceRegistry, request)
            .orElseThrow(() -> noReachableMemberException(request));
    return Address.create(serviceReference.host(), serviceReference.port());
  }

Avoid creating ClientChannel and ClientAdapter on critical path

RSocketServiceClientAdapter => ClientChannel is being created on every message we send. This redudant object allocation for no reason. All the logic which rSocket client adapter contains can be moved right to the RSocketClientTransport itself; there by giving chance to execute transformations right away with out intermediate objects.

API Gateway

Develop api gw to allow to access services from external clients and protocols

The api gatway is a member in the discovery group and allows to map requests to service endpoints

it works int the following modes:

OPTIMISTIC - by default

  1. transparent - user specify no custom mappings all messages expected to follow ServiceMessage format.
  2. mixed mode - user defined mapping will be respected else falback expects ServiceMessage formats.

PESSIMISTIC

  1. PESSIMISTIC - only user defined mappings will be defined all others will be rejected
// this example map any web socket request (**transparent mode**)
// the transparent expects messages to be in service message format that includes
// service qualifier and service data 
ApiGateway.builder().seeds(seeds).gateway().ws().port(8080)
  .build();
// this example map any web socket request **mixed-mode**:
// the none transparent mode map user defined api to service message only for mappings defined by user  
// ws is a bi-dicrectionl channel of downstream and upstream messages
// downstream() is a flux only for the request messages
// upstream() is a flux only for the response messages
// session is the ws-socket channel involved in this stream.   
ApiGateway.builder().seeds(seeds).gateway().ws().port(8080)
  .requests()
    .map((session , httpRequest)-> { return ServiceMessage.builder()
           .qualifier(understandWhatServiceFor(httpRequest))
           .data(req.data())
           .build(); 
   }).responses()
      .map((session,response)-> {
         socket.write(response.data());
  })
.build();
// this example map any web socket with specific url "/hello" request  
ApiGateway.builder().seeds(seeds).gateway().ws()
  .port(8080)
  .mode(PESSIMISTIC)
  .on("/hello")
  .requests().map((session , req)-> { return ServiceMessage.builder()
           .qualifier(request.url())
           .data(req.data())
           .build(); 
   })
   .responses().map((session,resp)-> {
         session.response(resp.data());
  })
.build();
// this example map any web socket with specific filter for post for url "/hello" request  
ApiGateway.builder().seeds(seeds).gateway()
     .http().post().on("/hello").map(req ->{
       return ServiceMessage.builder()
           .qualifier("io.scalecube.Greeting/sayHello")
           .data(req.data())
           .build(); 
   }).build();

Redudant methods found at ServiceCall class

Mono<Void> oneWay(ServiceMessage request, Address address)

Mono<ServiceMessage> requestOne(ServiceMessage request, Class<?> responseType, Address address)

Flux<ServiceMessage> requestMany(ServiceMessage request, Class<?> responseType, Address address)

Looks like those methods were added for sending on concrete address, bypassing phase router.route()

Why we can't just set a custom router (it's just a biFunction) at serviceCall. In this case public api would remain clear (w/o those overloaded variants) and client would have to have to use setup mechanism in order to do something custom.

Looping @ronenhamias

When connection is reset by peer ugly exception is thrown in the log

Handle this exception and provide more beautiful meaningful error.

E 0627-1715:37,211 test_remote_node_died_mono [reactor-tcp-client-epoll-11]
io.scalecube.services.exceptions.ConnectionClosedException: Connection closed
at io.scalecube.services.transport.rsocket.client.RSocketServiceClientAdapter.toConnectionClosedException(RSocketServiceClientAdapter.java:66) ~[classes/:?]
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:44) [reactor-core-3.1.6.RELEASE.jar:3.1.6.RELEASE]
at reactor.core.publisher.Mono.subscribe(Mono.java:3080) [reactor-core-3.1.6.RELEASE.jar:3.1.6.RELEASE]
at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onComplete(FluxSwitchIfEmpty.java:75) [reactor-core-3.1.6.RELEASE.jar:3.1.6.RELEASE]
at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:130) [reactor-core-3.1.6.RELEASE.jar:3.1.6.RELEASE]
at reactor.core.publisher.DirectProcessor$DirectInner.onComplete(DirectProcessor.java:313) [reactor-core-3.1.6.RELEASE.jar:3.1.6.RELEASE]
at reactor.core.publisher.DirectProcessor.onComplete(DirectProcessor.java:130) [reactor-core-3.1.6.RELEASE.jar:3.1.6.RELEASE]
at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:245) [reactor-core-3.1.6.RELEASE.jar:3.1.6.RELEASE]
at reactor.ipc.netty.FutureMono$FutureSubscription.operationComplete(FutureMono.java:293) [reactor-netty-0.7.8.RELEASE.jar:0.7.8.RELEASE]
at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:511) [netty-common-4.1.25.Final.jar:4.1.25.Final]
at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:485) [netty-common-4.1.25.Final.jar:4.1.25.Final]
at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:424) [netty-common-4.1.25.Final.jar:4.1.25.Final]
at io.netty.util.concurrent.DefaultPromise.trySuccess(DefaultPromise.java:103) [netty-common-4.1.25.Final.jar:4.1.25.Final]
at io.netty.channel.DefaultChannelPromise.trySuccess(DefaultChannelPromise.java:84) [netty-transport-4.1.25.Final.jar:4.1.25.Final]
at io.netty.channel.AbstractChannel$CloseFuture.setClosed(AbstractChannel.java:1148) [netty-transport-4.1.25.Final.jar:4.1.25.Final]
at io.netty.channel.AbstractChannel$AbstractUnsafe.doClose0(AbstractChannel.java:764) [netty-transport-4.1.25.Final.jar:4.1.25.Final]
at io.netty.channel.AbstractChannel$AbstractUnsafe.close(AbstractChannel.java:740) [netty-transport-4.1.25.Final.jar:4.1.25.Final]
at io.netty.channel.AbstractChannel$AbstractUnsafe.close(AbstractChannel.java:611) [netty-transport-4.1.25.Final.jar:4.1.25.Final]
at io.netty.channel.epoll.AbstractEpollChannel$AbstractEpollUnsafe.shutdownInput(AbstractEpollChannel.java:481) [netty-transport-native-epoll-4.1.25.Final-linux-x86_64.jar:4.1.25.Final]
at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:831) [netty-transport-native-epoll-4.1.25.Final-linux-x86_64.jar:4.1.25.Final]
at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:408) [netty-transport-native-epoll-4.1.25.Final-linux-x86_64.jar:4.1.25.Final]
at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:308) [netty-transport-native-epoll-4.1.25.Final-linux-x86_64.jar:4.1.25.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:884) [netty-common-4.1.25.Final.jar:4.1.25.Final]
at java.lang.Thread.run(Thread.java:748) [?:1.8.0_151]

modules names are not consistent found in project

these modules seem not to correspond with the rest of the modules in the projects

  • benchmarks
  • examples
  • rsocket-transport

i suggest to rename to the project prefix:

  • services-benchmarks
  • services-examples
  • services-rsocket-transport

so all modules will look the same.

Service transport must have 'resources' layer

It's not possible to inject custom resources into service transport layer. Hence it's not possible to control them, only what's left is to rely on transport itself; for example that it would properly manage netty's thread pool.

Finalize support of @Request type and @Response type

  • Make sure @RequestType is present only when parameter type is ServiceMessage; if prameter count 0 or parameter type is scalar or pojo then throw exception (yes, dont allow ambiguity).
  • The same as in prev. point applies fot @ResponseType.

Misconception of service provider modules

There's a module: scalecube-services-discovery, his api interface io.scalecube.services.discovery.api.ServiceDiscovery leaves under services-api. Ok. Great.

On the other hand modules: scalecube-services-transport, scalecube-services-codec leaves apart, i.e. their interfaces was defined in separate modules. On which services module depends.

Looks like we have different approaches. Need to come up with single one. Everyapi ***-api layer lives in services-api module or every api layers lives in corresponding modules on which service-api depends.

Decouple Discovery from services

Motivation

Scalecube services are using scalecube cluster as its main discovery provider.
decoupling the discovery from scalecube services will allow the user to choose or create its own discovery provider using a plug and play (ServiceLoader).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.