Giter Club home page Giter Club logo

memstate's Introduction

Memstate

Quickstart | Documentation | memstate.io/docs

Join the chat on Discord Build status NuGet Badge

What is Memstate?

In-memory event-sourced ACID-transactional replicated object graph engine. What? Can you say that again? Ok, it's an application server that keeps all your data in RAM. It runs without a database because all the transactions are recorded in a log and used to restore the state of the application at startup or when setting up a replica. You define the object model, commands and queries using C#. Besides being very simple Memstate is also very fast and will outperform any relational database.

Why Memstate?

Your data fits in RAM. Moving data back and forth between disk and memory is slow, difficult to maintain and error prone. Use Memstate to structure and manage your data in-memory, providing transparent persistence, high performance, high availability, concurrency control and transactions with strong ACID guarantees.

Memstate has many possible use cases but is designed primarily to handle complex OLTP workloads by replacing the datastore, data access layer and domain layer in a typical enterprise application.

The benefits are many:

  • Productivity/Cost - Way less code to write and maintain, typically less than 50%
  • Quality - strongly typed and compiled C# means less bugs and problems
  • Performance - In-memory is orders of magnitude faster than reading/writing to disk
  • Faster time to market, faster feedback cycles
  • Time travel! - restore to any point in time to debug, run a query
  • Distributed and highly available
  • Full history of every single change
  • Scalability - Scale out to handle massive
  • Cloud and microservices ready - Docker, VM
  • Open source
  • Cross platform - Built with .NET Core and runs on Linux, Mac and Windows
  • Free for commercial use - LGPL license

So what's the catch?

Your data model needs to fit in RAM on a single server or VM. When using replication, each node has a copy of the entire model. This means Memstate can't scale beyond the limits of a VM with maximum amount of RAM. At the time of writing AWS offers 4TB instances which should be sufficient for most OLTP workloads.

As the journal grows over time, replaying billions of commands can take a significant amount of time which means restoring a node or setting up a new replica could take several hours.

Did we mention how simple Memstate is to use?

        [Test]
        public async Task Most_compact_start_using_all_default_configurations()
        {
            var engine = await Engine.Start<LoyaltyDB>();
            Print(await engine.Execute(new InitCustomer(10, 10)));
            Print(await engine.Execute(new InitCustomer(20, 20)));
            Print(await engine.Execute(new TransferPoints(10, 20, 5)));
            await engine.DisposeAsync();

            // Produces the following output :)

            /*             
            Customer[10] balance 10 points.
            Customer[20] balance 20 points.
            Sent 5 points. | Sender, Customer[10] balance 5 points. | Recipient, Customer[20] balance 25 points.
            */
        }

        private void Print(object o)
        {
            Console.WriteLine(o.ToString());
        }

Quickstart - getting started

Quickstart

Governance, Support and Contributions

Memstate is an open source project sponsored and governed by Devrex Labs, an LLC based in Sweden. Devrex Labs provide commercial support and consulting services for OrigoDB and Memstate. Contributions are welcome, check out the issues (or add a new one) and discuss with the core team before getting started. You will need to sign our CLA using the CLA assistant

Background and Objectives

Devrex Labs also maintain OrigoDB, an in-memory database engine for .NET Framework. Memstate is a redesign based on our experience building and working with OrigoDB, taking the best parts, learning from mistakes and setting some new objectives. This section takes on a comparison with OrigoDB perspective.

  • Performance - We're aiming at 100K commands per second. Note that this is write operations, bounded by disk i/o. Read operations are cpu bound and can hit the millions per second depending on how complex the model is. (OrigoDB will max out at 3K writes per second (WPS).)

  • Simplified Replication - Memstate relies on a backing storage provider such as EventStore or PostgreSQL for message ordering. Each node simply subscribes to the ordered stream of commands from the underlying backing store and isn't aware of the other nodes. There is no primary or leader, each node can process both commands and queries.

Memstate's replication scheme has higher availability (because any node can accept writes) at the expense of some temporal inconsistency across the nodes.

  • Multi-platform support - Memstate is a .NET Standard 2.x and runs on Linux, Mac and Windows.

  • Streaming - We are aiming to support streaming of command/query results when the result type is IEnumerable. TBD

  • Reactive - Command execution triggers sytem and user-defined events which are pushed to subscribing tcp clients.

  • Async processing model - In order to achieve higher throughput, Memstate writes and flushes commands in batches, relying heavily on async/await for concurrency.

  • Cloud ready - solid support for monitoring, control, observability, cloud based storage, prebaked VM images on Azure and AWS. - TBD

  • Docker support - Memstate can run in a docker container (see Dockerfile) and we aim to provide official docker images. Did anyone say stateful microservices?

  • Smart configuration - Using the new .NET Core configuration support, the default settings can be overriden with JSON files, environment variables or command line arguments.

Features

In-memory object graph

Your data fits in RAM. Define your own object model and operations (commands and queries) or use a built-in model such as redis, keyvalue, graph, relational, document, sparse array, etc. Also we have some cool geo-spatial types which you can use in your custom model.

Event-sourced

All operations (commands) are written to persistent storage and used to restore the state of the in-memory object graph when a node starts up. The log is a complete audit trail of every single change to the system and can be used to recreate the state of the graph at any given point in time. A point-in-time snapshot can be used for queries or historical debugging.

Real-time event notifications

Domain events emitted from within the model can be subscribed to (with optional server side filtering) by local or remote clients. This enables reactive applicatons with real time updates when data is changed.

Configurable serialization formats

Choose JSON for readability and interoperability or a binary format for speed and smaller size.

ACID Transactions

  • Atomicity - The Command is the fundamental transactional unit. Command authors must ensure that the in-memory model is left unchanged in the case of an exception. Memstate can be configured to shut down in the case of a runtime exception.

  • Consistency - Each command needs to be deterministic. It must depend only on it's parameters and the current state of the model. After command execution the model is in the next consistent state.

  • Isolation - Single writer/multiple reader model is the default. Commands execute one at a time, thus fully serialized isolation level.

  • Durability - write-ahead logging of commands to a persistent storage backend. memstate is as durable as the backend. Choose from eventstore, postgres or file system.

Configurable Storage Backend

Memstate relies on a backing storage provider for persistence and global message ordering. We currently support EventStore, PostgreSQL and plain old file system storage.

  • EventStore - The recommended default backend, a perfect fit in terms of performance, durability, reliability, extensibility and interoperability.

  • PostgreSQL - PostgreSQL notifications are used to push commands to the the replicas. PostgreSQL is the recommended choice if you're already running it and have your operations in place or want to use an enterprise-ready, fully managed instance on either AWS or Azure.

  • File system - simple append only journaling. Single memstate node only.

Memstate in the press

memstate's People

Contributors

baskarmib avatar erinloy avatar gitter-badger avatar goblinfactory avatar hagbarddenstore avatar pashcovich avatar rofr avatar shubhranshu avatar simcon avatar vannevelj 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  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  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  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  avatar  avatar  avatar  avatar  avatar  avatar

memstate's Issues

Speed up unit tests

Unit tests in Memstate.Test take an unnecessary long time to run.

  • It looks like this might be down to GeopointTests. maybe due to the custom test case provider which appears to hang the test runner, not the tests itself. The result is that running the unit tests takes around a minute to run wheras all the tests should be running within milliseconds each.

  • the result is that I'm not able to sort the tests by duration since the problem appears to be the interaction with the runner, not the test itself. According to the measured tests the tests themselves run very quickly.

  • have not tested this with Ncrunch, but having something unstable means I don't want to crunch (i.e. have these tests running all the time).

  • sometimes running the tests leaves the test runner in an unstable state. (needs more info). I'll update this with more information when this card gets pulled into play.

  • This tests also causes 46 warnings, unit test element ... reported as active although it's already finished (Success)
    screen shot 2018-03-06 at 19 53 53

host executable

A thin wrapper taking a config file and/or command line args and hosting a server node

Locking options

Bt default, the kernel uses a global ReaderWriterLockSlim to allow either a single writer or multiple readers at any given time. This is a simple but powerful locking model that guarantees serializable isolation. But a major drawback is that command/query execution time will add to latency and any long running operation will block throughput.

A simple scheme to address this issue would be to used named locks. A given command/query is either associated with a named lock, or will default to the global lock. This will allow more granular locking, bypassing locks and other relaxed locking options.

And if the user has a thread safe model, we should be able to disable locking altogether. So an ILockingStrategy interface with NullLockingStrategy and ReaderWriterLockSlimStrategy implementations looks like a good approach.

OrigoDB has an immutability model where the entire model is immutable, and commands return a new model. This might at some point be ported to memstate. However, using locking options as proposed above would allow portions of the model to be immutable. Example:

public class MyModel
{
   ImmutableList<Customer> Customers {get;set;}
}

public class AddCustomer : Command<MyModel>
{
   public Customer Customer{get;set;}

   public void Execute(MyModel model)
   {
      model.Customers = model.Customers.Add(Customer);
   }
}

The huge benefit here is that a query can grab a reference to the Customers list and will get a point in time snapshot without blocking concurrent AddCustomer commands.

memstate.io web site

copy origodb.com site and tweak from there. origodb.com is based on jekyll, host with firebase because gh-pages does not (yet) support https for custom domains.

Use callbacks for setting properties on settings objects

Settings are populated by the configuration subsystem. This means users don't create the settings objects, instead they obtain a reference to them by calling Config.Current.GetSettings<TSettings>().

This is ugly but can be hidden from the user by accepting an Action to various methods, for example var engine = Engine.For<MyModel>((EngineSettings s) => s.StreamName="myname") ; or Config.Current.Configure<EngineSettings>(s => s.StreamName)

Xunit tests broken on appveyor and cake

powershell ./build.ps1 -Target Test

========================================
Test
========================================
Executing task: Test
xUnit.net Console Runner (64-bit .NET 4.0.30319.42000)
System.InvalidOperationException: Unknown test framework: could not find xunit.dll (v1) or xunit.execution.*.dll (v2) in
 C:\onedrive\git\memstate\EventStore.Tests\bin\Debug\netcoreapp1.1
System.IO.FileNotFoundException: Could not load file or assembly 'System.Runtime, Version=4.1.0.0, Culture=neutral, Publ
icKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified.
System.InvalidOperationException: Unknown test framework: could not find xunit.dll (v1) or xunit.execution.*.dll (v2) in
 C:\onedrive\git\memstate\EventStore.Tests\obj\Debug\netcoreapp1.1
System.InvalidOperationException: Unknown test framework: could not find xunit.dll (v1) or xunit.execution.*.dll (v2) in
 C:\onedrive\git\memstate\Memstate.Tests\bin\Debug\netcoreapp1.1
System.IO.FileNotFoundException: Could not load file or assembly 'Memstate.Tests, Version=0.0.0.0' or one of its depende
ncies. The system cannot find the file specified.
System.InvalidOperationException: Unknown test framework: could not find xunit.dll (v1) or xunit.execution.*.dll (v2) in
 C:\onedrive\git\memstate\Memstate.Tests\obj\Debug\netcoreapp1.1
System.InvalidOperationException: Unknown test framework: could not find xunit.dll (v1) or xunit.execution.*.dll (v2) in
 C:\onedrive\git\memstate\Memstate.Tests\obj\Debug\netcoreapp1.1
An error occurred when executing task 'Test'.
Error: xUnit.net (v2): Process returned an error (exit code 1).

Design and implement Isolation

Queries and Commands may carry mutable objects reachable from the outside into the model or return mutable references to objects within the model breaking isolation.

One extreme is to serialize all incoming commands and outgoing results, the other extreme is to document the phenomenon well and let users deal with it. Somewhere in between, intelligent or configurable seems like a reasonable approach. OrigoDB takes this approach but the design could be improved. Here's a quote from the docs:

There are two types of isolation to consider when using OrigoDB: * Isolation between transactions * Isolation of the model from user code when running OrigoDB in-process

Commands are executed sequentially, thus fully isolated. The default ISynchronizer uses a ReaderWriterLockSlim to allow either a single writer or multiple readers at any given time. This guarantees that reads always see the most recent state (and that the state is not modified) for the entire duration of the transaction.

By default, commands, command results and query results are cloned to prevent leaking references to mutable objects within the model. Cloning uses serialization/deserialization and can have a significant impact on performance. By designing for isolation all or some of the cloning can be disabled. See Configuration/Isolation for details on how to configure what gets cloned and not.

And here's the documentation page on isolation: http://origodb.com/docs/core-0.19/configuration/isolation/

When running memstate server, as opposed to in-process, all objects in and out are serialized over the wire. In this case isolation is ALMOST completely solved. The exception is if a mutable reference is returned from an operation, there is a small time window where corruption can take place: after the kernel releases the lock and before the result object is serialized.

Subscription or CommandExecuted event

Can you clarify the difference between using a subscription:

var sub = config.GetStorageProvider().CreateJournalSubscriptionSource();
sub.Subscribe(nextRecord, record =>
{
}

and using the CommandExecute event:

Engine.CommandExecuted += EngineOnCommandExecuted;
void EngineOnCommandExecuted(JournalRecord journalrecord, bool islocal, IEnumerable<Event> events)
{
}

Also whether there's a difference at startup / playback?

Benchmark test hangs

Hangs for both postgres and eventstore providers.
Only writes a single event to eventstore and then hangs.
Postgres benchmark produces output then hangs at the end of the benchmark run.

Default JsonSerializerAdapter to record 1 line per journal entry instead of 1 batch per line.

json-example.journal.txt

failing test

(Update : these tests are now out of date.)

Given a memstate db configured to write journal extries as json, 1 line of json per journal entry
When I run a few commands
then the journal file should exist
And the journal file should have saved the entries as text, 1 line per entry

The following code below produces the attached file (see atop) If you open the text file and see multiple lines, turn off word wrap :D

        [Test]
        public async Task Simple_end_to_end_with_human_readable_json_journal_file()
        {
            Print("Given a memstate db configured to write journal extries as json, 1 line of json per journal entry");
            var settings = new MemstateSettings { StreamName = "json-example" };
            settings.Serializers.Register("a-unique-key", _ => new JsonSerializerAdapter(settings));
            settings.Serializer = "a-unique-key";
            var model1 = await new EngineBuilder(settings).BuildAsync<LedgerDB>();

            Print("When I run a few commands");
            await model1.ExecuteAsync(new InitAccount(accountNumber: 1, openingBalance: 100.11M, currency: '£'));
            await model1.ExecuteAsync(new InitAccount(accountNumber: 2, openingBalance: 200.22M, currency: '£'));
            await model1.ExecuteAsync(new Transfer(fromAccount: 1, toAccount: 2, amount: 0.01M, currency:'£'));
            var accounts = await model1.ExecuteAsync(new GetAccounts());
            await model1.DisposeAsync();
            accounts.ForEach(Console.WriteLine);

            Print("then the journal file should exist");
            Assert.True(File.Exists("json-example.journal"));

            Print("And the journal file should have saved the entries as text, 1 line per entry");
            var lines = File.ReadAllLines("json-example.journal");
            Assert.AreEqual(3, lines.Length);
            StringAssert.Contains("M100.11", lines[0]);
            StringAssert.Contains("M200.22", lines[1]);
            StringAssert.Contains("M0.01", lines[2]);

            // to view the journal file in an editor comment out the File.Delete in the test Setup/teardown.
            // The journal file will be located in 
            // Memstate.Docs.GettingStarted\bin\Debug\netcoreapp2.0\json-example.journal
        }

CI/CD pipeline

Build and push nuget packages on demand or based on release branch. appveyor? circleci? cake build?

Capture all existing implemented features as "Requirement" tests.

draft suggestion for recording Requirements

Refactor the project structure in order to capture requirements as executable acceptance tests, so that anyone volunteering to work on memstate can know the status of what features have been built, and are currently available to use and work (based on the current release).

  • requirements should be written at a high enough level describing the required benefits that the feature should deliver, and not describe the how, so that if we change the specific implementation the test should as far as possible not require much changing.

  • requirements for any feature that a dev is going to work on should be written before any code is written. (so that we can make sure the requirements are clear and fully understood.)

  • Suggestion : requirements can be attached to the issue or project board and recorded in Gherkin syntax, see example below

  • be written as BDD style acceptance tests so that we can run the tests as part of CICD.

  • acceptance tests should contain sufficient examples that anyone new can easily see the intention of the feature.

  • the final acceptance test that is written should show the simplest code possible that proves that the system behaves as the documentation describes. (see example at the bottom) This is really to prove to a user that the behaviour works. The real proof for us, will be done through a complete set of unit, integration, performance, load and security tests. These tests will cover a wide set of edge cases that are not necessary to include in the acceptance tests

  • the acceptance tests should use as few Mocks as possible to simulate as far as possible the actual code that a user could put together in a simple console application. (so that the tests can serve as valuable and real how-to code examples.)

  • there will be some minor duplication between work on the requirements and the documentation. The difference between the 'docs' and 'requirements' is that the docs and getting started focuses on a slice through memstate and covers only the basics. Requirements are written 'per feature', every time someone extends memstate with new functionality. If a new piece of functionality is something that a user needs to be aware of from the very beginning of using memstate then we'd update the documentation at the same time.

sample feature expressed in Gherkin

# sample `feature` text notes for a feature, saved in the  github project or issue
# this get's translated into a C# test when a developer starts to work on the issue or feature.
# these are written and reviewed *before* development starts. 

Epic : Account/ClosingAccount
Feature : user cannot close account with a negative balance

Given an account with <balance>
when the account is closed()
then the result is <result>

Examples

    balance | result
    --------| ------
    [NULL]  | closed
    0       | closed
    -10     | exception
    10      | closed

acceptance test translated to C#

  • in this example, someone pulled a card from the backlog called, "user cannot close account with negative balance". This sample shows that features can have more features (e.g. epics) and can be modified over time, and still keep all the tests and requirements together in one place.
  • rough sample below showing suggested folder structure and test naming for epics
  • class ClosingAccount would reside in a folder in file /Memstate.Requirements/Account/ClosingAccount.cs
  • this convention allows us to have any amount of nesting as appropriate on a case by case basis.
  namespace Memstate.Requirements.Account
{

  // using nested classes for epics to group all the requirements in one place
   public class ClosingAccount
  {
     [TestCase(null)]
     [TestCase(0M)]
     [TestCase(100M)]
      [Test]
       public void user_closing_account_with_positive_zero_or_null_balance_should_close_account(decimal balance)
      {
          var account = new Account(balance);
          var result = account.Close();
          Assert.AreEqual(result, Result.Closed);
      }
 
     [Test]
       public void user_closing_account_with_negative_balance_should_throw_exception()
      {
          var account = new Account(-10M);
         Assert.Throws<Exception>( a=> account.Close());
      }
 
 }

}

Suggestion above based on a gitter discussion where I proposed updating the project to record requirements using the same syntax I use for requirements in my Konsole project here : https://github.com/goblinfactory/konsole/blob/master/Konsole.Tests/WindowTests/SplitTests.cs

Refactor code for readability and maintenance

  1. Ensure all files only contain a single type (Inner types allowed).
  2. Rename MemstateServer and MemstateClient to follow a convention.
  3. Remove unused usings.
  4. Remove unused types, methods, properties and fields.

benchmark tests

Tests that measure throughput and tail latency for various workloads

Split file journal into chunks

The file journal is a single file that grows indefinitely. We want to have it in manageably sized chunks and a naming scheme that reflects the ordering and recordnumbers, eg my-journal.00000042.000023414.journal

Tool to convert an OrigoDB journal to memstate

This would be a .NET Framework console or windows application that references OrigoDB (with modules) and memstate (with all modules). It will need to dynamically reference the assembly containing types serialized to the journal.

RecordNumber off by one errors

RecordNumber should start from 0 or 1 but must be consistent across all storage providers. Currently, eventstore starts with 0, postgres starts with 1.
Consolidate storage providers to use 0 and fix engine/builder/storage interfaces accordingly.

event store subsciption fails intermittently

Failed CanWriteManyAndReadFromMany(Provider:Memstate.EventStore.EventStoreProvider, Memstate.EventStore, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null, Serializer: Memstate.JsonNet.JsonSerializerAdapter, Memstate.JsonNet, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null, Name:memstatebae4e34664)
Error Message:
Expected: 300
But was: 0

Stack Trace:
at System.Test.ClusterTests.d__4.MoveNext() in /Users/rf/memstate/src/System.Test/ClusterTests.cs:line 136

Standard Output Messages:
C: Provider:Memstate.EventStore.EventStoreProvider, Memstate.EventStore, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null, Serializer: Memstate.JsonNet.JsonSerializerAdapter, Memstate.JsonNet, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null, Name:memstateb34b2a1454
[23,11:55:07.856,DEBUG] Deserialization to EventStore.ClientAPI.Messages.ClientMessage+StreamEventAppeared failed : System.MissingMethodException: No parameterless constructor defined for this object.
at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor)
at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
at System.Activator.CreateInstance(Type type, Boolean nonPublic)
at System.Activator.CreateInstance(Type type)
at ProtoBuf.Serializers.TypeSerializer.CreateInstance(ProtoReader source, Boolean includeLocalCallback) in C:\code\protobuf-net\src\protobuf-net\Serializers\TypeSerializer.cs:line 314
at ProtoBuf.Serializers.TypeSerializer.Read(Object value, ProtoReader source) in C:\code\protobuf-net\src\protobuf-net\Serializers\TypeSerializer.cs:line 209
at ProtoBuf.ProtoReader.ReadTypedObject(Object value, Int32 key, ProtoReader reader, Type type) in C:\code\protobuf-net\src\protobuf-net\ProtoReader.cs:line 607
at proto_16(Object , ProtoReader )
at ProtoBuf.Meta.TypeModel.DeserializeCore(ProtoReader reader, Type type, Object value, Boolean noAutoCreate) in C:\code\protobuf-net\src\protobuf-net\Meta\TypeModel.cs:line 748
at ProtoBuf.Meta.TypeModel.Deserialize(Stream source, Object value, Type type, SerializationContext context) in C:\code\protobuf-net\src\protobuf-net\Meta\TypeModel.cs:line 606
at ProtoBuf.Serializer.Deserialize[T](Stream source) in C:\code\protobuf-net\src\protobuf-net\Serializer.cs:line 84
at EventStore.ClientAPI.Transport.Tcp.ProtobufExtensions.Deserialize[T](ArraySegment`1 data)

Replace XUnit with NUnit Tests

Replace all the Xunit tests with nunit.

There's no need to have both Xunit and nunit. Nunit appears to have better support for automating .net standard tests in Cake.

Getting started documentation is out of date

Specifically : Sample commands and DTOs are no longer valid.
Should be immutable.
Customer.cs sample code is missing.
Remove anon constructor from sample Command.
Update commentary.

Error during serialization using Json serializer

I've found an issue where a null value in an 'exotic' type from a third party library doesn't get deserialized correctly due to the settings for Json.NET.

I've posted a spike to my fork:

https://github.com/Simcon/memstate/commit/71870e91aad95a5a3d23bf6803b5b47b56c87501

Check out the addition of NullValueHandling = NullValueHandling.Ignore to the JsonSerializerAdapter constructor. This fixes the issue in the corresponding test.

The simple fix is to add the fix to mainline. Would this be possible, @rofr ?

The 'right' fix would be to allow specifying serialization settings but this non-trivial as far as I can see.

JSONSerializer Numbers default to Int64

        [Fact]
        public void Int32_can_be_cloned()
        {
            int i = 42;
            var serializer = new JsonSerializerAdapter();
            var stream = new MemoryStream();
            serializer.WriteObject(stream, i);
            stream.Position = 0;
            object o = serializer.ReadObject(stream);
            Assert.IsType<int>(o); //fails, o is long
        }

        [Fact]
        public void Int64_can_be_cloned()
        {
            long i = 42;
            var serializer = new JsonSerializerAdapter();
            var stream = new MemoryStream();
            serializer.WriteObject(stream, i);
            stream.Position = 0;
            object o = serializer.ReadObject(stream);
            Assert.IsType<long>(o); //succeeds
        }

embedded web ui

Add an http endpoint to memstate server exposing metrics, dashboard, etc. Future version could have the ability to execute commands/queries, browse data.

Documentation

Port origodb docs, modify accordingly. Use GH flavored markdown in the docs sub folder.

Metrics

Implement built-in metrics using the AppMetrics library.

Start with the following metrics:

  • Execution time of queries (Histogram)
  • Execution time of commands executed by kernel (Histogram)
  • Execution time of commands executed by engine (Histogram)
  • Commands pending (Gauge)
  • Commands executed (Counter)
  • Queries executed (Counter)
  • Failed commands (Counter)
  • Failed queries (Counter)

Native memstate StorageProvider with pluggable backing store

Separate journal writer process that accepts journal entries/batches over tcp, commits to pluggable backing store and notifies connected memstate engines. This process will be responsible for global orderings of commands and assigning record numbers. Can only run as a single process so needs to be lightweight and load quickly to ensure availability.

Update proxy tests to use interfaces

OrigoDB models derived from MarshalByRefObject and used the built in proxy. Memstate proxy feature is based on System.ReflectionDispatchProxy which can only proxy interfaces. The tests need to be updated to use interface based models.

Command / Query API over HTTP

Implement JSON/XML over HTTP for commands and queries. This will allow access from any client that can speak http.

Json serializer does not create a journal file that can be read (NotSupportedException)

When using JsonSerializerAdapter and default settings, the resulting generated journal file cannot be read.

The test below fails with;

System.NotSupportedException : Specified method is not supported.
   at Memstate.JsonNet.SurrogateConverter.ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer) in C:\src\git-alan-public\memstate-fork-alan\Memstate.JsonNet\SurrogateConverter.cs:line 43

Steps to reproduce the error, run this test

        [Test]
        public async Task Json_file_created_should_be_able_to_be_deserialized()
        {
            Print("Given a journal file");
            var settings = new MemstateSettings { StreamName = "json-example2" };
            settings.Serializers.Register("a-unique-key", _ => new JsonSerializerAdapter(settings));
            settings.Serializer = "a-unique-key";
            var model1 = await new EngineBuilder(settings).BuildAsync<LedgerDB>();

            await model1.ExecuteAsync(new InitAccount(accountNumber: 1, openingBalance: 100.11M, currency: '£'));
            await model1.ExecuteAsync(new InitAccount(accountNumber: 2, openingBalance: 200.22M, currency: '£'));
            await model1.ExecuteAsync(new Transfer(fromAccount: 1, toAccount: 2, amount: 0.01M, currency: '£'));
            var accounts1 = await model1.ExecuteAsync(new GetAccounts());
            await model1.DisposeAsync();
            Assert.True(File.Exists("json-example2.journal"));

            Print("When I create an new LedgerDB that will read the recently created ledger file");
            var model2 = await new EngineBuilder(settings).BuildAsync<LedgerDB>();

            Print("then the journal entries should have been read in");
            var accounts2 = await model2.ExecuteAsync(new GetAccounts());
            Assert.AreEqual(2, accounts2.Count);

            Print("And the in memory object database should be back to the latest state");
            CollectionAssert.AreEqual(accounts1, accounts2);

        }

You can use any Engine<T> with any commands. The important point is the serializer settings.

Partial journal loading

When the engine starts, the entire journal is replayed. We should support uses cases where we load a portion of the journal by providing arguments such as LoadFromRecord, LoadToRecord, LoadFromPointInTime, LoadToPointInTime.

In the case that we do not read to the end, the engine should be in readonly mode, or perhaps start writing a new stream.

Nuget packaging strategy and implementation

Design and implement nuget packaging and naming strategy. We want a core package with 0 dependencies besides NET Standard. Memstate.Core is not a good name, it can be confused with dotnet core. How about Memstate.Minimal?

Modules could have Module in the name. For example:

  • Memstate.Modules.EventStore
  • Memstate.Modules.Postgres

And an easy to get started, single package with everything bundled. This package would also contain versions verified to work together. What would be a good name?

What about 3rd party contributions? Memstate.Contrib.*?

System tests that verify replication across multiple nodes

Storage Providers

  • EventStore
  • PostgreSql

Note: InMemoryStorage and FileStorage do not support multiple nodes.

Test cases

  • Start multiple engines from an empty store, execute commands on a single engine, verify replication to other engines
  • Execute commands on multiple engines, verify that all nodes have identical models
  • Start single engine, execute commands, start additional engines, ensure they are in sync

Update QuickStart sample code Commands to not hide base ID property so that command can be immutable

Update getting started Commands to not hide base ID property so that command can be immutable.

The demo commands in src/Memstate.Docs.GettingStarted/QuickStart/Commands/EarnPoints.cs are mutable, because they can't be serialized due to a problem caused by hiding the base ID property.

If the Command DTO's in the quickstart getting started guide are changed to not hide the ID property then they can be made into proper immutable classes, and that would be fabulous, the way it's supposed to be!

Backing store for pgsql

Using the pgsql notfications feature we can notify replicas when new commands are written.

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.