Skip to content

Commit fcd39f6

Browse files
committed
protobuf for the journal
1 parent 0bebf18 commit fcd39f6

File tree

6 files changed

+221
-80
lines changed

6 files changed

+221
-80
lines changed
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using Modules.ProtoBuf.Test.Domain;
5+
using NUnit.Framework;
6+
using OrigoDB.Core;
7+
using OrigoDB.Core.Journaling;
8+
using OrigoDB.Core.Test;
9+
using OrigoDB.Modules.ProtoBuf;
10+
using ProtoBuf;
11+
using ProtoBuf.Meta;
12+
13+
namespace Modules.ProtoBuf.Test
14+
{
15+
[ProtoContract(SkipConstructor = true)]
16+
public class AddItemCommand : Command<TodoModel, Guid>
17+
{
18+
[ProtoMember(1)]
19+
public readonly string ItemName;
20+
21+
public AddItemCommand(string itemName)
22+
{
23+
ItemName = itemName;
24+
}
25+
26+
public override Guid Execute(TodoModel model)
27+
{
28+
return model.AddItem(ItemName);
29+
}
30+
}
31+
32+
public class RemoveItemCommand : Command<TodoModel>
33+
{
34+
public readonly Guid Id;
35+
36+
public RemoveItemCommand(Guid id)
37+
{
38+
Id = id;
39+
}
40+
41+
public override void Execute(TodoModel model)
42+
{
43+
model.Items.Remove(Id);
44+
}
45+
}
46+
47+
[TestFixture]
48+
public class JournalFormattingTests
49+
{
50+
EngineConfiguration _config;
51+
IStore _store;
52+
53+
[SetUp]
54+
public void Setup()
55+
{
56+
_config = new EngineConfiguration().ForIsolatedTest();
57+
_config.PacketOptions = PacketOptions.Checksum;
58+
59+
//assign unique ints to commands
60+
var commandTypeTags = new Dictionary<Type, int>
61+
{
62+
{typeof(AddItemCommand), 1},
63+
{typeof(RemoveItemCommand), 2}
64+
};
65+
66+
//dynamic registration of command type (no attributes)
67+
var typeModel = TypeModel.Create();
68+
MetaType mt = typeModel.Add(typeof(RemoveItemCommand), false)
69+
.Add(1, "Id");
70+
mt.UseConstructor = false;
71+
72+
73+
ProtoBufFormatter.ConfigureJournaling(_config, commandTypeTags, typeModel);
74+
75+
_store = _config.CreateStore();
76+
}
77+
78+
[Test]
79+
public void CanWriteaRollbackMarkerAndReadBack()
80+
{
81+
var writer = _store.CreateJournalWriter(0);
82+
writer.Write(new JournalEntry<RollbackMarker>(32, new RollbackMarker()));
83+
var resurrected = _store.GetJournalEntries().Single();
84+
Assert.AreEqual(32, resurrected.Id);
85+
}
86+
87+
[Test]
88+
public void CanWriteModelCreatedEntryAndReadBack()
89+
{
90+
var writer = _store.CreateJournalWriter(0);
91+
writer.Write(new JournalEntry<ModelCreated>(32, new ModelCreated(typeof(TodoModel))));
92+
var resurrected = _store.GetJournalEntries().Single();
93+
Assert.AreEqual(32, resurrected.Id);
94+
Assert.AreEqual(typeof(TodoModel),(resurrected as JournalEntry<ModelCreated>).Item.Type);
95+
}
96+
97+
[Test]
98+
public void CanWriteCommandToJournalAndReadBack()
99+
{
100+
const string itemName = "Fish";
101+
var entryTimestamp = DateTime.Now;
102+
103+
var writer = _store.CreateJournalWriter(0);
104+
var entry = new JournalEntry<Command>(1, new AddItemCommand(itemName), entryTimestamp);
105+
writer.Write(entry);
106+
var resurrected = _store.GetJournalEntries().Single();
107+
108+
Assert.AreEqual(entryTimestamp, resurrected.Created);
109+
Assert.AreEqual(1, resurrected.Id);
110+
Assert.IsInstanceOf<JournalEntry<Command>>(resurrected);
111+
var typedEntry = (JournalEntry<Command>)resurrected;
112+
Assert.IsInstanceOf<AddItemCommand>(typedEntry.Item);
113+
var typedCommand = (AddItemCommand)typedEntry.Item;
114+
Assert.AreEqual(itemName, typedCommand.ItemName);
115+
}
116+
}
117+
}

OrigoDB.Modules.Protobuf.Test/OrigoDB.Modules.ProtoBuf.Test.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
<Compile Include="Framework\SerializationHelper.cs" />
6363
<Compile Include="Domain\Employee.cs" />
6464
<Compile Include="Domain\Company.cs" />
65+
<Compile Include="JournalFormattingTests.cs" />
6566
<Compile Include="Properties\AssemblyInfo.cs" />
6667
<Compile Include="ProtoBufFormatterTests.cs" />
6768
<Compile Include="TodoModelTests.cs" />

OrigoDB.Modules.Protobuf/OrigoDB.Modules.Protobuf.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@
5252
<Reference Include="System.Xml" />
5353
</ItemGroup>
5454
<ItemGroup>
55-
<Compile Include="ProtoBufFormatterException.cs" />
5655
<Compile Include="Properties\AssemblyInfo.cs" />
5756
<Compile Include="ProtoBufFormatter.cs" />
57+
<Compile Include="TypeModelExtensions.cs" />
5858
</ItemGroup>
5959
<ItemGroup>
6060
<None Include="packages.config" />

OrigoDB.Modules.Protobuf/ProtoBufFormatter.cs

Lines changed: 30 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Runtime.Serialization;
34
using System.IO;
45
using System.Text;
56
using OrigoDB.Core;
6-
using OrigoDB.Core.Journaling;
77
using ProtoBuf;
88
using ProtoBuf.Meta;
99
using OrigoDB.Core.Utilities;
@@ -38,15 +38,16 @@ public class ProtoBufFormatter : IFormatter
3838
/// </summary>
3939
public SerializationBinder Binder
4040
{
41-
get; set;
41+
get;
42+
set;
4243
}
4344

4445
/// <summary>
4546
/// The associated streaming context.
4647
/// </summary>
4748
public StreamingContext Context
4849
{
49-
get;
50+
get;
5051
set;
5152
}
5253

@@ -70,18 +71,6 @@ public ProtoBufFormatter(RuntimeTypeModel typeModel = null, bool includeTypeName
7071
Context = new StreamingContext(StreamingContextStates.Persistence);
7172
}
7273

73-
/// <summary>
74-
/// Dynamically configure the origodb types written to the journal
75-
/// </summary>
76-
private void RegisterJournalTypes()
77-
{
78-
//TODO: incomplete
79-
var je = _typeModel.Add(typeof(JournalEntry), false);
80-
je.AddField(1, "Id");
81-
je.AddField(2, "Created");
82-
je.AddSubType(3, typeof(JournalEntry<RollbackMarker>));
83-
je.AddSubType(4, typeof(JournalEntry<Command>)).Add(1, "Item");
84-
}
8574

8675
/// <summary>
8776
/// Deserializes the data on the provided stream and
@@ -112,7 +101,7 @@ public void Serialize(Stream stream, object graph)
112101
Ensure.NotNull(stream, "stream");
113102
Ensure.NotNull(graph, "graph");
114103

115-
if (IncludeTypeName)
104+
if (IncludeTypeName)
116105
new BinaryWriter(stream, Encoding.UTF8)
117106
.Write(graph.GetType().AssemblyQualifiedName);
118107

@@ -139,41 +128,37 @@ protected virtual Type GetType(Stream stream)
139128
}
140129

141130
/// <summary>
142-
/// Modify the given configuration to use ProtoBuf formatting
131+
/// Modify the given configuration to use ProtoBuf formatting to clone results
143132
/// </summary>
144133
/// <param name="config"></param>
145-
/// <param name="usage">the usage to configure</param>
146134
/// <param name="typeModel">An optional typemodel</param>
147-
public static void Configure(EngineConfiguration config, FormatterUsage usage, RuntimeTypeModel typeModel = null)
135+
public static void ConfigureResultCloning(EngineConfiguration config, RuntimeTypeModel typeModel = null)
148136
{
149-
switch (usage)
150-
{
151-
case FormatterUsage.Default:
152-
throw new NotSupportedException("Call with a specific usage, not FormatterUsage.Default");
153-
case FormatterUsage.Snapshot:
154-
throw new NotSupportedException("Can't configure for snapshots without a type parameter, call ConfigureSnapshots<T> passing the type of the model instead");
155-
case FormatterUsage.Journal:
156-
config.SetFormatterFactory((cfg, fu) => new ProtoBufFormatter<JournalEntry>(typeModel: typeModel, useLengthPrefix: true), usage);
157-
break;
158-
case FormatterUsage.Results:
159-
config.SetFormatterFactory((cfg,fu) => new ProtoBufFormatter(typeModel, true, true), usage);
160-
break;
161-
case FormatterUsage.Messages:
162-
throw new NotSupportedException("Not supported in this version of the module");
163-
default:
164-
throw new ArgumentOutOfRangeException("usage");
165-
}
137+
config.SetFormatterFactory((cfg, fu) => new ProtoBufFormatter(typeModel, true, true), FormatterUsage.Results);
166138
}
167139

168140
/// <summary>
169-
/// Modify the given configuration to use ProtoBuf formatting for snapshots.
141+
/// Modify the given configuration to use ProtoBuf formatting for snapshots of type T.
170142
/// </summary>
171143
/// <typeparam name="T">The concrete type of the model</typeparam>
172-
/// <param name="config">the configuration to modify</param>
173-
/// <param name="typeModel">An optional runtime type configuration</param>
174-
public static void ConfigureSnapshots<T>(EngineConfiguration config, RuntimeTypeModel typeModel)
144+
public static void ConfigureSnapshots<T>(EngineConfiguration config, RuntimeTypeModel typeModel = null) where T : Model
175145
{
176-
config.SetFormatterFactory((cfg,fu) => new ProtoBufFormatter<T>(typeModel: typeModel), FormatterUsage.Snapshot);
146+
config.SetFormatterFactory((cfg, fu) => new ProtoBufFormatter<T>(typeModel), FormatterUsage.Snapshot);
147+
}
148+
149+
/// <summary>
150+
/// Modify the given EngineConfiguration to use ProtoBuf for journaling. Pass unique ints for each type of command.
151+
/// The id's must be maintained across versions of your assembly.
152+
/// </summary>
153+
public static void ConfigureJournaling(EngineConfiguration config, IDictionary<Type, int> commandTypeTags, RuntimeTypeModel typeModel = null)
154+
{
155+
config.SetFormatterFactory((cfg, fu) =>
156+
{
157+
var formatter = new ProtoBufFormatter<JournalEntry>(typeModel, includeTypeName: false, useLengthPrefix: true);
158+
typeModel.RegisterCommandSubTypes(commandTypeTags);
159+
return formatter;
160+
},
161+
FormatterUsage.Journal);
177162
}
178163
}
179164

@@ -186,11 +171,9 @@ public sealed class ProtoBufFormatter<T> : ProtoBufFormatter
186171
/// <summary>
187172
/// Create a typed formatter. Type name won't be prepended to the stream because the type is known.
188173
/// </summary>
189-
/// <param name="typeModel"></param>
190-
/// <param name="useLengthPrefix"></param>
191-
public ProtoBufFormatter(RuntimeTypeModel typeModel = null, bool useLengthPrefix = false)
192-
: base(includeTypeName: false, typeModel: typeModel, useLengthPrefix: useLengthPrefix)
193-
{}
174+
public ProtoBufFormatter(RuntimeTypeModel typeModel = null, bool useLengthPrefix = false, bool includeTypeName = false)
175+
: base(includeTypeName: includeTypeName, typeModel: typeModel, useLengthPrefix: useLengthPrefix)
176+
{ }
194177

195178
/// <summary>
196179
/// Derive the type from the generic type parameter as opposed to reading the type name from the stream
@@ -199,7 +182,7 @@ public ProtoBufFormatter(RuntimeTypeModel typeModel = null, bool useLengthPrefix
199182
/// <returns></returns>
200183
protected override Type GetType(Stream stream)
201184
{
202-
return typeof (T);
185+
return typeof(T);
203186
}
204187
}
205188
}

OrigoDB.Modules.Protobuf/ProtoBufFormatterException.cs

Lines changed: 0 additions & 32 deletions
This file was deleted.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using OrigoDB.Core;
5+
using OrigoDB.Core.Journaling;
6+
using ProtoBuf.Meta;
7+
8+
namespace OrigoDB.Modules.ProtoBuf
9+
{
10+
/// <summary>
11+
/// Extension methods
12+
/// </summary>
13+
public static class TypeModelExtensions
14+
{
15+
/// <summary>
16+
/// Inejct contracts for origodb core types into given RuntimeTypeModel. Can safely be called multiple times.
17+
/// </summary>
18+
/// <param name="typeModel"></param>
19+
public static void RegisterFrameworkTypes(this RuntimeTypeModel typeModel)
20+
{
21+
if (typeModel.IsDefined(typeof (Command))) return;
22+
23+
typeModel.Add(typeof(Command), true);
24+
25+
var journalEntry = typeModel.Add(typeof (JournalEntry), false)
26+
.Add(1, "Id")
27+
.Add(2, "Created");
28+
29+
journalEntry.UseConstructor = false;
30+
31+
journalEntry.AddSubType(3, typeof(JournalEntry<Command>));
32+
journalEntry.AddSubType(4, typeof (JournalEntry<RollbackMarker>));
33+
journalEntry.AddSubType(5, typeof(JournalEntry<ModelCreated>));
34+
35+
36+
typeModel.Add(typeof(JournalEntry<Command>), false)
37+
.Add(1, "Item")
38+
.UseConstructor = false;
39+
40+
typeModel.Add(typeof (JournalEntry<ModelCreated>), false)
41+
.Add(1, "Item")
42+
.UseConstructor = false;
43+
44+
typeModel.Add(typeof (JournalEntry<RollbackMarker>), false)
45+
.Add(1, "Item")
46+
.UseConstructor = false;
47+
48+
typeModel.Add(typeof (ModelCreated), false)
49+
.Add(1, "Type")
50+
.UseConstructor = false;
51+
52+
typeModel.Add(typeof(RollbackMarker), false)
53+
.UseConstructor = false;
54+
55+
}
56+
57+
/// <summary>
58+
/// Register the passed commands as sub classes of OrigoDB.Core.Command. The id must be unique for each command
59+
/// and constant across versions of your system. If any of the command types are already registered nothing will happen.
60+
/// </summary>
61+
public static void RegisterCommandSubTypes(this RuntimeTypeModel typeModel, IDictionary<Type, int> commandIdsByType)
62+
{
63+
RegisterFrameworkTypes(typeModel);
64+
var commandMeta = typeModel[typeof(Command)];
65+
if (commandMeta.GetSubtypes().Any(st => commandIdsByType.ContainsKey(st.DerivedType.Type))) return;
66+
foreach (var type in commandIdsByType.Keys)
67+
{
68+
commandMeta.AddSubType(commandIdsByType[type], type);
69+
}
70+
}
71+
}
72+
}

0 commit comments

Comments
 (0)