Code Monkey home page Code Monkey logo

muoncore / newton Goto Github PK

View Code? Open in Web Editor NEW
12.0 9.0 5.0 489 KB

Extend Muon into the world of Spring. Implement Muon apis, especially around events to provide DDD patterns natively in Spring Boot

Home Page: http://muoncore.io

License: GNU Lesser General Public License v3.0

Makefile 0.29% Java 85.58% Groovy 14.14%
events event-sourcing eventstore domain-driven-design ddd cqrs stream-processing saga-process-manager aggregate-root spring-boot muon spring java-8

newton's Introduction

Master Build

License

Status

muon spring

What is Muon

Microservices today are optimised for building systems that can continue to change as you need them to. To achieve this, you decompose an application into its component parts, then recompose it back together again using some form of integration technology. The most common form of integration today is RPC based, whether HTTP, gRPC or one of its friends.

RPC is a fairly simplistic model for integration, with well known flaws and foibles. The other options, messaging and event collaboration are much harder to get started with, and so are often avoided until the "v2 rewrite". Muon lets you adopt messaging and events from the start of a project, by making it easy to do the right thing with them.

Muon is fully polyglot and enables you to build and use much richer message based APIs to recompose your distributed system in highly performant, rich communication styles, beyond RPC.

Muon Spring lets you use, expose and interact with these APIs in various forms and gives helpers for using them in a Spring project. It has particular support for using persisted event streams as described at https://github.com/muoncore/stack-event

Event Systems

The Muon Event Protocol provides a muon api for creating and consuming persisted event streams. These can be used to implement Event Sourcing, stream processing and other techniques.

Muon Spring uses this API to provide a set of DDD patterns. Notably :-

  • Event Sourced Aggregates

  • Stream Processed Views

  • Distributed Sagas.

It enables you to build highly complex transactional systems that are naturally distributed, event oriented and give event based transactional semantics.

These integrate well with the other Muon APIs, enabling you to interact easily with other services written in other languages.

newton's People

Contributors

bobjana avatar camw avatar daviddawson avatar fiqwrk avatar robpurcell avatar teamcity-muoncore avatar

Stargazers

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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

newton's Issues

event domain service needs more control over replay

When a new domain service is created in an existing system it is unlikely that you would want to replay all historical events on first deploy. After first deploy then you want the resilience that replay from last seem gives you.

This should be an option on basesubscriber.

Debugging using breakpoints causes any further event processing from happening

Muon Version: 0.0.5
Language: Java

Services in Use: photon, newton

As soon as a consumer of newton gets into a state which causes exception to be thrown while processing events coming from eventstore (such as event subscription), the eventstore gets into a state where any further submissions cannot be proccessed with the following type of exception:

_ “Failed to persist domain event **Event(...):A timeout occurred, the remote service did not send a response”,_

To reproduce:

  • Run photon-sample app in debug mode. Place a breakpoint in say CreateTaskCommand.execute() l.41
  • Run TaskSpecification
  • Upon hitting breakpoint give it about 10 seconds before resuming
  • Remove breakpoint
  • Re-run TaskSpecification

Endless loop when submitting an event during another event being processed

Example:

public class TeamPolicyConfigurationAdaptor extends EventDrivenDomainService {

	private MongoTemplate mongoTemplate;
	private EventClient eventClient;

	@Autowired
	public TeamPolicyConfigurationAdaptor(StreamSubscriptionManager streamSubscriptionManager, MongoTemplate mongoTemplate, EventClient eventClient) {
		super(streamSubscriptionManager);
		this.mongoTemplate = mongoTemplate;
		this.eventClient = eventClient;
	}

	@Override
	protected String[] eventStreams() {
		return new String[]{
			"core-service/Policy",
			"core-service/Team",
		};
	}

	@EventHandler
	public void handle(PolicyCreatedEvent event) {
		if (event.getConfiguration() != null) {
			this.mongoTemplate.save(new TeamPolicyConfiguration(event.getId(), event.getConfiguration()));
		}
	}

	@EventHandler
	public void handle(PolicyConfigurationChangedEvent event) {
		final TeamPolicyConfiguration teamPolicyConfiguration = this.mongoTemplate.findById(event.getId(), TeamPolicyConfiguration.class);
		teamPolicyConfiguration.setPolicyVersion(event.getVersion());
		teamPolicyConfiguration.setPolicyConfiguration(event.getConfiguration());
		this.mongoTemplate.save(teamPolicyConfiguration);

		teamPolicyConfiguration.getTeams().forEach(teamId -> {
			TeamPolicyConfigurationChangedEvent changedEvent = new TeamPolicyConfigurationChangedEvent(teamId, event.getId(), teamPolicyConfiguration.getPolicyVersion(), teamPolicyConfiguration.getPolicyConfiguration());
			changedEvent.setTenantId(event.getTenantId());
			eventClient.eventAsync(ClientEvent.ofType(changedEvent.getClass().getSimpleName()).stream("core-service/Team").payload(changedEvent).build());
		});

	}

	@EventHandler
	public void handle(TeamAssignedToPolicyEvent event) {
		log.error(">>>>>>>>>>>>>>>> Team '{}' assigned to policy '{}'", event.getId(), event.getPolicyId());
		final TeamPolicyConfiguration config = this.mongoTemplate.findById(event.getPolicyId(), TeamPolicyConfiguration.class);
		config.getTeams().add(event.getId());
		this.mongoTemplate.save(config);

		event.getOldPolicyId().ifPresent(oldPolicyId -> {
			final TeamPolicyConfiguration oldConfig = this.mongoTemplate.findById(oldPolicyId, TeamPolicyConfiguration.class);
			oldConfig.getTeams().remove(event.getId());
			this.mongoTemplate.save(oldConfig);
		});

		TeamPolicyConfigurationChangedEvent changedEvent = new TeamPolicyConfigurationChangedEvent(event.getId(), event.getPolicyId(), config.getPolicyVersion(), config.getPolicyConfiguration());
		changedEvent.setTenantId(event.getTenantId());
//		eventClient.eventAsync(ClientEvent.ofType(event.getClass().getSimpleName()).stream("core-service/Team").payload(changedEvent).build());
	}

	@Data
	@Document(collection = "_TeamPolicyConfigurationAdaptor")
	private static final class TeamPolicyConfiguration {

		@Id
		@NonNull
		private DocumentId policyId;
		@NonNull
		private Configuration policyConfiguration;
		private int policyVersion = 1;
		private Set<DocumentId> teams = new HashSet<>();

	}

}

Need a timed event trigger

Should be able to emit an event at an arbitrary point in time.

This will be used in Sagas to have timeout failures and other time based continuations.

Aggregate snapshots

Currently, aggregates are fully rebuilt from the event stream every time.

Add a snapshot capability to load the majority of the state from, then replay just the incremental events.

Should run based on a policy defined on the aggregate root.

@AggregateConfiguration(context = "something", eventsBetweenSnapshot=5)

Unable to load aggregate even though it exists

io.muoncore.newton.eventsource.AggregateNotFoundException: Unable to load aggregate with ID 593f988039e08183f26b29d7, it does not exist in the event store

even though the entity exists as part of view:

"_id" : ObjectId("593f988039e08183f26b29d7"), "_class" : "mu.cibecs.core.device.query.view.Device",

This error also occurs inconsistently.

To replicate:

Run Cibecs DeviceSpecification. It fails when trying to create a task for the device. What's really strange is that the same aggregate is loaded for scenarios before 'Add device task', but then fails. Putting in a Thread.sleep(2000) causes it to pass

OR

Run new-sample TaskSpecification - the updateDescription scenario updates the description x25 times. If bug is not reproduced, just rerun spec a couple times

Remove mongo

Muon Version: 0.6.0
Language: Java

Services in Use:

Replace with a more lightweight db such as H2 as default fallback & integration testing purposes.

Ensure H2 config does not override customer provided config

Restarting photon causes any further event processing to fail on clients

2017-06-11 04:03:17.599 DEBUG 89713 --- [o-auto-1-exec-8] .w.s.m.s.DefaultHandlerExceptionResolver : Resolving exception from handler [public org.springframework.http.ResponseEntity<?> mu.cibecs.security.customer.CustomerController.create(mu.cibecs.security.customer.CustomerController$CreateCustomerRequest) throws java.util.concurrent.ExecutionException,java.lang.InterruptedException]: io.muoncore.exception.MuonException: Failed to persist domain event CustomerCreatedEvent(id=593ca4e439e0815e7f6137f7, domain=qui, name=Gibson, Gibson and Gibson):**A timeout occurred, the remote service did not send a response**
2017-06-11 04:03:17.600 ERROR 89713 --- [o-auto-1-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is io.muoncore.exception.MuonException: Failed to persist domain event CustomerCreatedEvent(id=593ca4e439e0815e7f6137f7, domain=qui, name=Gibson, Gibson and Gibson):A timeout occurred, the remote service did not send a response] with root cause

io.muoncore.exception.MuonException: Failed to persist domain event CustomerCreatedEvent(id=593ca4e439e0815e7f6137f7, domain=qui, name=Gibson, Gibson and Gibson):A timeout occurred, the remote service did not send a response
	at io.muoncore.protocol.event.client.AggregateEventClient.lambda$publishDomainEvents$0(AggregateEventClient.java:45)

Only was client app restarts it is able to process events

Auto register sagas in the application context

Sagas currently need these annotations

@Scope("prototype")
  @Component
  @SagaStreamConfig(streams = {"TestAggregate", "user/SagaTestAggregate"})
  public static class ComplexSaga extends StatefulSaga<OrderRequestedEvent> {

Should just need

@SagaStreamConfig(streams = {"TestAggregate", "user/SagaTestAggregate"})

Update @EnableNewton to scan for these annotations and load them into the app context

Implement a system wide subscription system that allows more efficient subscribing of streams to avoid duplicate traffic in most cases

Currently every subscription is independent.

If 10 services are subscribed to the same stream, they will all be sent the data, leading to 10 times as much traffic.

Since most of them will remain in lockstep most of the time, a single subscription could be used, most of the time, distributing the events out as they are received.

The only time this will not be the case is when a service needs to perform a full or partial replay of a stream. Eg, If a recovery is required or a view is being upgraded.

In this case, a separate subscription should be created, then when the subscription approaches the current main subcription, it should be shut down and the service then start taking events from the main subscription.

Starting client app when photon is unavailable causes undesirable failures

Should try to obtain global locks in background & load stream indexes only when photon is available

7-06 08:09:08.748  INFO 25446 --- [service/Profile] i.m.newton.cluster.LocalOnlyLockService  : Global lock 'ProfileView-core-service/Profile' released! Will try again in 500ms
2017-07-06 08:09:08.748  INFO 25446 --- [queEmailService] i.m.newton.cluster.LocalOnlyLockService  : Global lock 'ProfileUniqueEmailService' released! Will try again in 500ms
2017-07-06 08:09:08.748  INFO 25446 --- [-service/Device] i.m.newton.cluster.LocalOnlyLockService  : Global lock 'OrganizationUnitView-core-service/Device' released! Will try again in 500ms
2017-07-06 08:09:08.962  INFO 25446 --- [-service/Device] i.m.newton.cluster.LocalOnlyLockService  : Waiting on the lock DeviceLookupService-core-service/Device
2017-07-06 08:09:08.962  INFO 25446 --- [-service/Device] i.m.newton.cluster.LocalOnlyLockService  : Obtained global lock 'DeviceLookupService-core-service/Device', executing local task on this node
2017-07-06 08:09:08.963  WARN 25446 --- [-service/Device] i.m.newton.cluster.LocalOnlyLockService  : Locked process has failed with an exception, and DeviceLookupService-core-service/Device has been unlocked
2017-07-06 08:09:08.963  WARN 25446 --- [-service/Device] i.m.newton.cluster.LocalOnlyLockService  : Locking Process failed with exception

java.lang.IllegalStateException: state should be: open
	at com.mongodb.assertions.Assertions.isTrue(Assertions.java:70)
	at com.mongodb.connection.BaseCluster.selectServer(BaseCluster.java:82)
	at com.mongodb.binding.ClusterBinding$ClusterBindingConnectionSource.<init>(ClusterBinding.java:75)
	at com.mongodb.binding.ClusterBinding$ClusterBindingConnectionSource.<init>(ClusterBinding.java:71)
	at com.mongodb.binding.ClusterBinding.getReadConnectionSource(ClusterBinding.java:63)
	at com.mongodb.operation.OperationHelper.withConnection(OperationHelper.java:402)
	at com.mongodb.operation.FindOperation.execute(FindOperation.java:510)
	at com.mongodb.operation.FindOperation.execute(FindOperation.java:81)
	at com.mongodb.Mongo.execute(Mongo.java:836)
	at com.mongodb.Mongo$2.execute(Mongo.java:823)
	at com.mongodb.DBCursor.initializeCursor(DBCursor.java:870)
	at com.mongodb.DBCursor.hasNext(DBCursor.java:142)
	at com.mongodb.DBCursor.one(DBCursor.java:679)
	at com.mongodb.DBCollection.findOne(DBCollection.java:833)
	at com.mongodb.DBCollection.findOne(DBCollection.java:796)
	at com.mongodb.DBCollection.findOne(DBCollection.java:743)
	at org.springframework.data.mongodb.core.MongoTemplate$FindOneCallback.doInCollection(MongoTemplate.java:2185)
	at org.springframework.data.mongodb.core.MongoTemplate$FindOneCallback.doInCollection(MongoTemplate.java:2169)
	at org.springframework.data.mongodb.core.MongoTemplate.executeFindOneInternal(MongoTemplate.java:1921)
	at org.springframework.data.mongodb.core.MongoTemplate.doFindOne(MongoTemplate.java:1732)
	at org.springframework.data.mongodb.core.MongoTemplate.findById(MongoTemplate.java:652)
	at org.springframework.data.mongodb.core.MongoTemplate.findById(MongoTemplate.java:645)
	at mu.cibecs.support.newton.NewtonConfiguration$1.findOneById(NewtonConfiguration.java:39)
	at io.muoncore.newton.cluster.MuonClusterAwareTrackingSubscriptionManager.getEventStreamIndex(MuonClusterAwareTrackingSubscriptionManager.java:122)
	at io.muoncore.newton.cluster.MuonClusterAwareTrackingSubscriptionManager.localTrackingSubscription(MuonClusterAwareTrackingSubscriptionManager.java:87)
	at io.muoncore.newton.cluster.MuonClusterAwareTrackingSubscriptionManager.lambda$globallyUniqueSubscription$4(MuonClusterAwareTrackingSubscriptionManager.java:67)
	at io.muoncore.newton.cluster.LocalOnlyLockService.lambda$executeAndRepeatWithLock$1(LocalOnlyLockService.java:32)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

2017-07-06 08:09:08.963  INFO 25446 --- [-service/Device] i.m.newton.cluster.LocalOnlyLockService  : Global lock 'DeviceLookupService-core-service/Device' released! Will try again in 500ms
Disconnected from the target VM, address: '127.0.0.1:53933', transport: 'socket'

Add alternate master/consensus

Currently uses jgroups, not appropriate for managed environments like cloud foundry.

Integrate with some other system, etcd, zookeper et al.

Add a return path for the Command Bus

The command bus should allow the invoking code to know when the command has finished, and what the result was.

This should be in the form of success/fail and the emitted events.

Event source sagas.

Current StatefulSaga is persisted. Would be good to have an event sourced saga instead.

This would need to ensure that commands and interests are idempotently applied or ignored during replay/ reconstruction.

Also integrate with snapshotting as implemented for #3

Remove SagaBus and replace with a Saga Registry

Saga bus is used to explicitly start sagas. Given that sagas can be implicitly started via an event, this seems to be redundant as the implicit start is the expected and preferred model.

Remove the saga bus.

Instead, implement a registry that enables you to look up sagas by the id of the initialising event

based on this code internally

OrderRequestedEvent ev = new OrderRequestedEvent();
SagaCreated sagaCreated = sagaRepository.getSagasCreatedByEventId(ev.getId()).get(0);
SagaMonitor<DocumentId, TestSaga> monitor = sagaFactory.monitor(sagaCreated.getSagaId(), TestSaga.class);

Handle upgraded event classes

If an event class is renamed or its schema changes, then currently this causes a failure, either a class not found or data can be lost during replay for older events.

Implement a mechanism that handles upgrading of event schema/ types in a few ways and document the upgrade paths that are available.

Event not on classpath throws Nullpointer when processed outside originating bounded context

2017-06-22 11:51:04.519 INFO 439 --- [ channel-4] nClusterAwareTrackingSubscriptionManager : Event{eventType='AdministratorCreatedEvent', streamName='security-service/Customer', schema='null', causedById=null, causedByRelation='null', service='security-service', orderId=37, eventTime=1498125022716}
java.lang.NullPointerException
at io.muoncore.protocol.event.Event.getPayload(Event.java:83)
at io.muoncore.newton.cluster.MuonClusterAwareTrackingSubscriptionManager.lambda$localTrackingSubscription$7(MuonClusterAwareTrackingSubscriptionManager.java:107)
at io.muoncore.newton.cluster.MuonClusterAwareTrackingSubscriptionManager$EventSubscriber.onNext(MuonClusterAwareTrackingSubscriptionManager.java:136)
at io.muoncore.newton.cluster.MuonClusterAwareTrackingSubscriptionManager$EventSubscriber.onNext(MuonClusterAwareTrackingSubscriptionManager.java:119)
at io.muoncore.protocol.event.client.DefaultEventClient$1.onNext(DefaultEventClient.java:144)
at io.muoncore.protocol.event.client.DefaultEventClient$1.onNext(DefaultEventClient.java:134)
at io.muoncore.protocol.reactivestream.client.ReactiveStreamClientProtocol.handleMessage(ReactiveStreamClientProtocol.java:102)
at io.muoncore.channel.impl.WiretapChannel$2.lambda$send$0(WiretapChannel.java:64)
at io.muoncore.transport.client.RingBufferLocalDispatcher.route(RingBufferLocalDispatcher.java:404)
at io.muoncore.transport.client.RingBufferLocalDispatcher$SingleThreadTask.run(RingBufferLocalDispatcher.java:297)
at io.muoncore.transport.client.RingBufferLocalDispatcher$3.onEvent(RingBufferLocalDispatcher.java:171)
at io.muoncore.transport.client.RingBufferLocalDispatcher$3.onEvent(RingBufferLocalDispatcher.java:168)
at reactor.jarjar.com.lmax.disruptor.BatchEventProcessor.run(BatchEventProcessor.java:128)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:748)

Add hashing algorithm to load balance work in stream subscriptions

Permit active active by distributing event processing around the active members.

Implement using a hash bucket/ hash ring scheme so that each node can individually decide to process. May require tracking stream indeces on a hash bucket basis rather than per stream.

Need to consider hash bucket failover and how scaling up/ down should work. Probably defineany buckets and distribute the evenly, although need to ensure obly 1 sub is active per node, rather than per bucket.

Add a LocalOnlyLockService

Some environments (containerised overlay networks) don't support UDP at all and fail.
Add a non clustering lock service that enables running as a single node in any environment.

Automatically generate the repositories

Every aggregate repo now looks like this

@Service
class InviteRepository extends MuonEventSourceRepository<Invite> {
    InviteRepository(AggregateEventClient aggregateEventClient,
                     EventClient eventClient, @Value("application.name") String applicationName) {
        super(Invite, aggregateEventClient, eventClient, applicationName)
    }
}

These can be fully auto generated from the AggregateRoots and injected into the app context. The Autowire as a generic interface rather than a concrete type.

"Error with registering a saga in the start cache" log statement is misleading

Causing QA to question whether or not this is an error. We should consider change log level & make the message less "error-centric" so that it's not picked up in any log tracing tools

7/11/2017 9:41:22 AM2017-07-11 07:41:22.786  INFO 12 --- [ool-35-thread-1] io.muoncore.newton.saga.SagaStartCache   : Adding saga to start class mu.cibecs.core.storageserver.StorageServerTeamAssignmentSaga on class mu.cibecs.core.team.events.TeamAssignedToStorageServerEvent
7/11/2017 9:41:22 AM2017-07-11 07:41:22.786 DEBUG 12 --- [ool-35-thread-1] io.muoncore.newton.saga.SagaStartCache   : Error with registering a saga in the start cache. It exists already!
7/11/2017 9:41:22 AM2017-07-11 07:41:22.787  INFO 12 --- [ool-35-thread-1] io.muoncore.newton.saga.SagaStartCache   : Adding saga to start class mu.cibecs.core.policy.PolicyTeamAssignmentSaga on class mu.cibecs.core.team.events.TeamAssignedToPolicyEvent
7/11/2017 9:41:22 AM2017-07-11 07:41:22.787 DEBUG 12 --- [ool-35-thread-1] io.muoncore.newton.saga.SagaStartCache   : Error with registering a saga in the start cache. It exists already!
7/11/2017 9:41:22 AM2017-07-11 07:41:22.787  INFO 12 --- [ool-35-thread-1] io.muoncore.newton.saga.SagaStartCache   : Adding saga to start class mu.cibecs.core.profile.ProfileDeviceAssignmentSaga on class mu.cibecs.core.device.events.DeviceAssignedToProfileEvent
7/11/2017 9:41:22 AM2017-07-11 07:41:22.788 DEBUG 12 --- [ool-35-thread-1] io.muoncore.newton.saga.SagaStartCache   : Error with registering a saga in the start cache. It exists already!
7/11/2017 9:41:22 AM2017-07-11 07:41:22.788  INFO 12 --- [ool-35-thread-1] io.muoncore.newton.saga.SagaStartCache   : Adding saga to start class mu.cibecs.core.device.DeviceRegistrationSaga on class mu.cibecs.core.device.events.DeviceCreatedEvent
7/11/2017 9:41:22 AM2017-07-11 07:41:22.788 DEBUG 12 --- [ool-35-thread-1] io.muoncore.newton.saga.SagaStartCache   : Error with registering a saga in the start cache. It exists already!
7/11/2017 9:41:22 AM2017-07-11 07:41:22.789  INFO 12 --- [ool-35-thread-1] io.muoncore.newton.saga.SagaStartCache   : Adding saga to start class mu.cibecs.core.team.TeamProfileAssignmentSaga on class mu.cibecs.core.profile.events.ProfileAssignedToTeamEvent
7/11/2017 9:41:22 AM2017-07-11 07:41:22.789 DEBUG 12 --- [ool-35-thread-1] io.muoncore.newton.saga.SagaStartCache   : Error with registering a saga in the start cache. It exists already!
7/11/2017 9:41:22 AM2017-07-11 07:41:22.789  INFO 12 --- [ool-35-thread-1] io.muoncore.newton.saga.SagaStartCache   : Adding saga to start class mu.cibecs.core.device.DeviceActivationSaga on class mu.cibecs.core.device.events.DeviceActivationRequestedEvent
7/11/2017 9:41:22 AM2017-07-11 07:41:22.790 DEBUG 12 --- [ool-35-thread-1] io.muoncore.newton.saga.SagaStartCache   : Error with registering a saga in the start cache. It exists already!

Fails to persist domain events: photon is unavailable

This still happens more than would be expected.

Photon server is running, rabbitMq as well so there is no env related reason why a client app shouldn't be able to persist an event to eventstore.

This seems to happen on relatively bigger payloads - Device.updateEndpointDetails

Captured the following logs which correspond around the same time (app +2hrs time diff)

Photon Logs:

rabbitmq                |
rabbitmq                | =WARNING REPORT==== 11-Jul-2017::14:32:42 ===
rabbitmq                | closing AMQP connection <0.30381.6> (172.20.0.1:41214 -> 172.20.0.3:5672):
rabbitmq                | client unexpectedly closed TCP connection
rabbitmq                |
rabbitmq                | =WARNING REPORT==== 11-Jul-2017::14:32:42 ===
rabbitmq                | closing AMQP connection <0.30365.6> (172.20.0.1:41212 -> 172.20.0.3:5672):
rabbitmq                | client unexpectedly closed TCP connection
photon_1                | 2017-07-11 14:32:47.227 DEBUG 1 --- [pool-8-thread-6] i.m.channel.impl.KeepAliveChannel        : Connection has failed to stay alive, last message was received 5506ms ago, sending failure to protocol level: shared-channel
photon_1                | 2017-07-11 14:32:47.227 DEBUG 1 --- [      channel-5] i.m.t.s.s.SharedSocketServerChannel      : Received a channel op closed message MuonMessage{id='0119670f-c37f-48c5-8731-b0ebc43e1b3e', created=1499783567227, targetServiceName='null', sourceServiceName='local', protocol='shared-channel', step='ChannelFailure', status=success, contentType='null', channelOperation=closed}
photon_1                | 2017-07-11 14:32:47.228 DEBUG 1 --- [ amqp-channel-1] i.m.e.a.r.RabbitMq09QueueListener        : Queue listener is cancelled:photonlite-receive-7dfaa394-0b1a-4041-bf4e-22da92ca4259
photon_1                | 2017-07-11 14:32:47.229 DEBUG 1 --- [      channel-5] i.m.t.s.s.SharedSocketServerChannel      : Received a channel op closed message MuonMessage{id='666cdf20-0484-4679-8d5f-1296f7e8fab0', created=1499783567228, targetServiceName='null', sourceServiceName='photonlite', protocol='null', step='ChannelFailure', status=success, contentType='text/plain', channelOperation=closed}``

Application logs:

2017-07-11 16:32:32.293 ERROR 43060 --- [nio-8081-exec-9] i.m.newton.command.SimpleCommandBus      : Unable to execute command: mu.cibecs.core.device.command.RegisterDeviceCommand

io.muoncore.exception.MuonException: Failed to persist domain event DeviceNameChangedEvent(id=5964e17f35f06aa834a5ea2c, name=nels.fay-pc):A timeout occurred, the remote service did not send a response
	at io.muoncore.newton.AggregateEventClient.lambda$publishDomainEvents$0(AggregateEventClient.java:50)
	at java.util.ArrayList.forEach(ArrayList.java:1249)
	at io.muoncore.newton.AggregateEventClient.publishDomainEvents(AggregateEventClient.java:40)
	at io.muoncore.newton.eventsource.muon.MuonEventSourceRepository.emitForAggregatePersistence(MuonEventSourceRepository.java:154)
	at io.muoncore.newton.eventsource.muon.MuonEventSourceRepository.save(MuonEventSourceRepository.java:83)
	at mu.cibecs.core.device.command.RegisterDeviceCommand.execute(RegisterDeviceCommand.java:88)
	at mu.cibecs.core.device.command.RegisterDeviceCommand.executeAndReturnEvents(RegisterDeviceCommand.java:68)
	at io.muoncore.newton.command.SimpleCommandBus.dispatch(SimpleCommandBus.java:27)
	at mu.cibecs.core.device.DeviceController.updateEndpointDetails(DeviceController.java:111)
	at mu.cibecs.core.device.DeviceController$$FastClassBySpringCGLIB$$31c2b12a.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:721)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
	at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:69)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:656)
	at mu.cibecs.core.device.DeviceController$$EnhancerBySpringCGLIB$$d992434f.updateEndpointDetails(<generated>)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133)
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:116)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
	at org.springframework.web.servlet.FrameworkServlet.doPut(FrameworkServlet.java:883)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:651)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:230)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
	at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:55)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
	at com.github.isrsal.logging.LoggingFilter.doFilterInternal(LoggingFilter.java:46)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
	at org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:108)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
	at mu.cibecs.common.support.hystrix.HystrixSetVariableFilter.doFilter(HystrixSetVariableFilter.java:28)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
	at mu.cibecs.common.support.hystrix.HystrixRequestContextEnablerFilter.doFilter(HystrixRequestContextEnablerFilter.java:23)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:317)
	at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:114)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	at org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter.doFilter(AbstractPreAuthenticatedProcessingFilter.java:121)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:170)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:214)
	at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:177)
	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
	at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:105)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
	at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
	at org.springframework.boot.actuate.autoconfigure.MetricsFilter.doFilterInternal(MetricsFilter.java:106)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:474)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:349)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:783)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:798)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1434)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Thread.java:745)

Sagas are being started more than once for the same event

Given: Device registration causing a DeviceCreatedEvent to be raised
Then: DeviceActivationSaga.start() is called twice

2017-06-22 15:27:56.376 DEBUG 18188 --- [ool-33-thread-1] mu.cibecs.core.DeviceRegistrationSaga    : Started registration saga for device: 594bc5dc1221a0470ca0b5ef
2017-06-22 15:27:56.376  WARN 18188 --- [ool-10-thread-1] m.c.common.tenant.TenantContextHolder    : Using legacy TenantContextHolder.
2017-06-22 15:27:56.376  WARN 18188 --- [ool-10-thread-1] m.c.common.tenant.TenantContextHolder    : Using legacy TenantContextHolder.
2017-06-22 15:27:56.376  INFO 18188 --- [      channel-4] nClusterAwareTrackingSubscriptionManager : Event{eventType='DeviceEndpointDetailsUpdatedEvent', streamName='core-service/Device', schema='null', causedById=null, causedByRelation='null', service='core-service', orderId=1304, eventTime=1498138076080}
2017-06-22 15:27:56.392  WARN 18188 --- [ool-10-thread-1] m.c.common.tenant.TenantContextHolder    : Using legacy TenantContextHolder.
2017-06-22 15:27:56.392  INFO 18188 --- [      channel-4] nClusterAwareTrackingSubscriptionManager : Event{eventType='DeviceEndpointDetailsUpdatedEvent', streamName='core-service/Device', schema='null', causedById=null, causedByRelation='null', service='core-service', orderId=1304, eventTime=1498138076080}
2017-06-22 15:27:56.392  WARN 18188 --- [ool-10-thread-1] m.c.common.tenant.TenantContextHolder    : Using legacy TenantContextHolder.
2017-06-22 15:27:56.392  WARN 18188 --- [ool-10-thread-1] m.c.common.tenant.TenantContextHolder    : Using legacy TenantContextHolder.
2017-06-22 15:27:56.392  WARN 18188 --- [ool-10-thread-1] m.c.common.tenant.TenantContextHolder    : Using legacy TenantContextHolder.
2017-06-22 15:27:56.392  WARN 18188 --- [ool-10-thread-1] m.c.common.tenant.TenantContextHolder    : Using legacy TenantContextHolder.
2017-06-22 15:27:56.392  WARN 18188 --- [ool-10-thread-1] m.c.common.tenant.TenantContextHolder    : Using legacy TenantContextHolder.
2017-06-22 15:27:56.392  WARN 18188 --- [ool-10-thread-1] m.c.common.tenant.TenantContextHolder    : Using legacy TenantContextHolder.
2017-06-22 15:27:56.407  WARN 18188 --- [ool-10-thread-1] m.c.common.tenant.TenantContextHolder    : Using legacy TenantContextHolder.
2017-06-22 15:27:56.407  INFO 18188 --- [      channel-4] nClusterAwareTrackingSubscriptionManager : Event{eventType='DeviceEndpointDetailsUpdatedEvent', streamName='core-service/Device', schema='null', causedById=null, causedByRelation='null', service='core-service', orderId=1304, eventTime=1498138076080}
2017-06-22 15:27:56.407  WARN 18188 --- [ool-10-thread-1] m.c.common.tenant.TenantContextHolder    : Using legacy TenantContextHolder.
2017-06-22 15:27:56.407  WARN 18188 --- [ool-10-thread-1] m.c.common.tenant.TenantContextHolder    : Using legacy TenantContextHolder.
2017-06-22 15:27:56.407  WARN 18188 --- [ool-10-thread-1] m.c.common.tenant.TenantContextHolder    : Using legacy TenantContextHolder.
2017-06-22 15:27:56.407  WARN 18188 --- [ool-10-thread-1] m.c.common.tenant.TenantContextHolder    : Using legacy TenantContextHolder.
2017-06-22 15:27:56.407  WARN 18188 --- [ool-10-thread-1] m.c.common.tenant.TenantContextHolder    : Using legacy TenantContextHolder.
2017-06-22 15:27:56.407  WARN 18188 --- [ool-10-thread-1] m.c.common.tenant.TenantContextHolder    : Using legacy TenantContextHolder.
2017-06-22 15:27:56.422  INFO 18188 --- [ool-33-thread-1] io.muoncore.newton.saga.SagaFactory      : Creating new saga of type class mu.cibecs.core.DeviceRegistrationSaga with payload DeviceCreatedEvent(id=594bc5dc1221a0470ca0b5ef, apiToken=eyJhbGciOiJIUzI1NiJ9.eyJleHBpcmVzIjo5MjIzMzcyMDM2ODU0Nzc1ODA3LCJjbGllbnRUeXBlIjoiREVWSUNFX1BST1RFQ1RJT04iLCJ0ZW5hbnROYW1lIjpudWxsLCJkaXNwbGF5TmFtZSI6bnVsbCwicm9sZXMiOlsiREVWSUNFX1BST1RFQ1RJT04iXSwidGVuYW50SWQiOiI1OGVjZWYwOWM1NDg5NGNiNzliOWUyMzkiLCJwcmluY2lwYWxJZCI6IjU5NGJjNWRjMTIyMWEwNDcwY2EwYjVlZiIsImV4cCI6OTIyMzM3MjAzNjg1NDc3NSwidXNlcm5hbWUiOiI1OTRiYzVkYzEyMjFhMDQ3MGNhMGI1ZWYifQ.mHJ-AY3bhcqxPtQL2znEYrCn7sQxfro9v4u9vKeuDgI)
2017-06-22 15:27:56.422  WARN 18188 --- [ool-10-thread-1] m.c.common.tenant.TenantContextHolder    : Using legacy TenantContextHolder.
2017-06-22 15:27:56.422  INFO 18188 --- [      channel-4] nClusterAwareTrackingSubscriptionManager : Event{eventType='DeviceEndpointDetailsUpdatedEvent', streamName='core-service/Device', schema='null', causedById=null, causedByRelation='null', service='core-service', orderId=1304, eventTime=1498138076080}
2017-06-22 15:27:56.424  WARN 18188 --- [ool-10-thread-1] m.c.common.tenant.TenantContextHolder    : Using legacy TenantContextHolder.
2017-06-22 15:27:56.424  INFO 18188 --- [      channel-4] nClusterAwareTrackingSubscriptionManager : Event{eventType='DeviceEndpointDetailsUpdatedEvent', streamName='core-service/Device', schema='null', causedById=null, causedByRelation='null', service='core-service', orderId=1304, eventTime=1498138076080}
2017-06-22 15:27:56.425 DEBUG 18188 --- [ool-33-thread-1] mu.cibecs.core.DeviceRegistrationSaga    : Started registration saga for device: 594bc5dc1221a0470ca0b5ef

screen shot 2017-06-22 at 3 29 04 pm

Introduce abstract Base Domain Service

so that it can abstract common boilerplate from views AND domain services.

  • Relates to "#24 remove @NewtonView"
  • simplify construction by passing the minimal amount of collaborators (currently requires StreamSubscriptionManager & EventStreamProcessor)
  • throw runtime exception upon construction failure

Start using the event provenance fields in the muon schema

During a saga (or maybe other processing), generating of new events should include a link to the event that "caused" the event to be emitted.

This is done by setting the "caused-by" field in the muon event schema.

Inferring causality may be difficult in many cases, but a generalised form should be made for sagas.

When running a sage responding to an event, the commands should be marked with the caused-by event id, and the events created during its execution stamped with that ID.

Expose aggregate stream as a stream

Rather than doing a full roll up internally, allow replaying the aggregate stream as a subscription.

This will allow the data to be interpreted live, UI push updates etc.

Add in default codec for DocumentId

currently dumps out internal information.

"id": {
    "value": {
      "timestamp": 1493376468,
      "machineIdentifier": 6840900,
      "processIdentifier": 8398,
      "counter": 6871490
    }
  },

Need to register a codec into GSON

Expose ReplayControl on StreamSubscriptionManager

It would be useful to be able to cancel an active subscription started on StreamSubscriptionManager

On the locking methods, this may be difficult to sync or manage effectively.

For now, exposing the EventReplayControl on localTrackingSubscription and localNonTrackingSubscription would help

Support SpEL in @AggregateConfig

Currently if no aggregate config is passed in, a spel expression of '${spring.application.name}' is used.

It would be useful to be able to support arbitrary expressions in the AggregateConfig.context param.

This would need to be evaluated within AggregateRootConfig when called from MuonEventSourceRepository.

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.