Brainix.GoalsCommon 1.0.2-beta-141

This is a prerelease version of Brainix.GoalsCommon.
There is a newer prerelease version of this package available.
See the version list below for details.
dotnet add package Brainix.GoalsCommon --version 1.0.2-beta-141                
NuGet\Install-Package Brainix.GoalsCommon -Version 1.0.2-beta-141                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Brainix.GoalsCommon" Version="1.0.2-beta-141" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Brainix.GoalsCommon --version 1.0.2-beta-141                
#r "nuget: Brainix.GoalsCommon, 1.0.2-beta-141"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Brainix.GoalsCommon as a Cake Addin
#addin nuget:?package=Brainix.GoalsCommon&version=1.0.2-beta-141&prerelease

// Install Brainix.GoalsCommon as a Cake Tool
#tool nuget:?package=Brainix.GoalsCommon&version=1.0.2-beta-141&prerelease                

Documentation

Summary

The GoalsCommon Package is a package that consists of three main parts: a RabbitMQ bus, the learning tree structure and a DataLoad helper.

RabbitMQ

Dependencies

The RabbitMQ bus has the following dependencies:

Installation

To install the GoalsCommon Package, the following command must be executed in the project's folder:

dotnet add package Brainix.GoalsCommon --prerelease

Initialization of the RabbitMQ bus

The RabbitMQ bus is to be controlled via the Brainix.GoalsCommon.RabbitMQ.Bus.IBrainixEventBus interface. There are two implementations for this in the Goals-Common package. The Brainix.GoalsCommon.RabbitMQ.Bus.DefaultRabbitMQBus class is used for normal communication with a RabbitMQ server. The Brainix.GoalsCommon.RabbitMQ.Bus.TestRabbitMQBus class was written to efficiently create integration tests with RabbitMQ.

DefaultRabbitMQBus

The DefaultRabbitMQBus is initialised with a RabbitMQConfiguration. This contains the complete ConnectionString and an ApplicationName. A normal initialisation would look like this:

using Brainix.GoalsCommon.RabbitMQ.Bus;
...
var brainixEventBus = new DefaultRabbitMQBus(new RabbitMQConfiguration(
	"amqp://username:password@url:port/",
	"serviceName"
));

In a real use case, the values for the ConnectionString and the ApplicationName would of course have to be chosen appropriately. A classic initialisation in the context of a dependency injection would look like this:

using Brainix.GoalsCommon.RabbitMQ.Bus;
...
services.AddSingleton<IBrainixEventBus, DefaultRabbitMQBus>(conf => new DefaultRabbitMQBus(
	new RabbitMQConfiguration(
		Configuration.GetConnectionString("rabbit_mq").Replace(<%password%>",vault.rabbitMQPassword),
		"TrainingCenter"
	)
));
TestRabbitMQBus

The TestRabbitMQBus is only initialised with a messageDelayInMs. The number of milliseconds by which the transmission of a new message to the consumer should be delayed is passed here. A classic initialisation in the context of a dependency injection would look as follows:

using Brainix.GoalsCommon.RabbitMQ.Bus;
...
services.AddSingleton<IBrainixEventBus, TestRabbitMQBus>((messageDelayInMs) => new TestRabbitMQBus(1));

BrainixEvents

All events that should be processed by the RabbitMQ bus must inherit from the generic, abstract class Brainix.GoalsCommon.RabbitMQ.Models.BrainixEvent. Each event shall contain a clearly defined data structure. The purpose of each event shall be explained in a short DocString. Each BrainixEvent also has the possibility to declare different event types. This can be done by declaring a public, internal enum with the name EventType for the respective BrainixEvent (Here it is very important that the name of the enum is written exactly like this). Here is a short example:

using System;

namespace Brainix.GoalsCommon.RabbitMQ.Models.DomainEvents
{
	/// <summary>
	///	This event is used for publishing Notifications that were produced by a specific user.
	/// </summary>
	public class NotificationEvent: BrainixEvent
	{
		public Guid SenderUserId { get; set; }
		public string NotificationContent { get; set; }
		public enum EventType
		{
			Information,
			Warning
		}
	}
}

Routing logic for BrainixEvents

The routing logic in RabbitMQ EventBus is based on topic exchanges and work queues. The underlying idea is that each event is published via its own exchange. Each individual microservice that wants to consume this event has its own queue that is bound to the respective exchange via one or more binding keys. The individual instances of the microservice consume directly from the queue. In concrete terms, this means that when a new Brainix event is published, an exchange with the name of the event is created. If a microservice specifies that it consumes an event, a queue is created and bound to the exchange. Here, all events - regardless of type - are consumed initially. If the consumer specifies that only a certain event types should be consumed, the binding key (between the exchange and the queue) will carry the names of the event types. From now on, only events of that types will be placed in the respective queue. This logic is illustrated by the following diagram: Diagram

Publish

To send an event, the method Publish<T>(T brainixEvent, Func<BrainixEventCallback, Task> callbackDelegate) where T : BrainixEvent must be called from the Brainix.GoalsCommon.RabbitMQ.Bus.IBrainixEventBus. This will take the event to be sent and optionally a callback function which will be called once the event has been successfully sent to the RabbitMQServer and the reception has been confirmed. By default, an event is initialised with the Default event type, where the EventTypeNumber has the value 0. If a special EventType should be set, then the EventTypeNumber property must be filled with the number of the matching element of the respective EventType enumeration. Here is a short example:

brainixEventBus.Publish(new NotificationEvent
{
	EventTypeNumber = (int) NotificationEvent.EventType.Information,
	SenderUserId = new Guid("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"),
    NotificationContent = "Hello World!"
}, async (brainixEventCallback) =>
{
    if(brainixEventCallback.Success){
	    //Handle successful publish
    }
    else
    {
	    //Handle unsuccessful publish
    }
});

Consume

To consume an event, the method Subscribe<T>(Func<T, Task> handlerFunction, List<int> eventTypeNumbers) where T : BrainixEvent must be called from the Brainix.GoalsCommon.RabbitMQ.Bus.IBrainixEventBus. This receives a handler function which is called as soon as a new event has been consumed. It should be noted that if this function cannot be successfully executed (an exception is caught), the event is put back into the queue and will no longer be consumed. It is also important to note that you cannot call the database directly from the handler function, because you are in a different thread and context. More information on this can be found in the corresponding documentation. Optionally, a list with the numbers of the respective event types that are to be consumed can be given to the method. This parameter determines the individual binding between the queues and exchanges that has already been explained. Here is a short example:

brainixEventBus.Subscribe<NotificationEvent>(async (notificationEvent) =>
{
    //Do something with notificationEvent
}, new List<int>
{
    (int) NotificationEvent.EventType.Information
});

Testing

For testing with the RabbitMQ bus, the IBrainixEventBus interface must be implemented by the Brainix.GoalsCommon.RabbitMQ.Bus.TestRabbitMQBus. Events can be sent and consumers defined in the integration test in the same way as in the normal implementation. However, it is important to note that the handler function of the consumer is always called in a different thread and thus executed with a time delay. This is especially important when writing to cross-thread objects within the integration test and comparing them with the expected object using Assert. It is advisable to pause the main thread for a few milliseconds to ensure that all other threads have successfully completed their work. Alternatively, you can do the ``assert'' directly in the handler function, which is cleaner in many cases, but does not cover the case where an event is never consumed.

KnowledgeTree

The structure of the knowledge tree is essentially divided into 3 parts - the basic models that form the knowledge tree (in Brainix.GoalsCommon.KnowledgeTree.Models), the outgrowing static Brainix.GoalsCommon.KnowledgeTree.KnowledgeTreeContent and the unit tests in Brainix.GoalsCommon.Test.KnowledgeTree.Tests.

Models

In the following, the basic models that make up the learning tree will be explained. The highest level of the learning tree currently consists of a Brainix.GoalsCommon.KnowledgeTree.Models.Curriculum. This contains an array of 3 Brainix.GoalsCommon.KnowledgeTree.Models.Category. These contain an array with an unspecified number of Brainix.GoalsCommon.KnowledgeTree.Models.Skill. The skills are the most important elements of the learning tree. They always have a SkillId, which is referenced in different parts of the system. The structure of the learning tree looks as follows:

Curriculum  ├─Category1    ├─SkillA    ├─SkillB    ├─SkillC  ├─Category2    ├─SkillD    ├─SkillE  ├─Category3    ├─SkillF    ├─SkillG

In addition, there is the Brainix.GoalsCommon.KnowledgeTree.Models.Categorization.SkillInstace class that inherits from Brainix.GoalsCommon.KnowledgeTree.Models.Skill. This contains an additional property Weight which is used to specify a weight for a skill. There is also the Brainix.GoalsCommon.KnowledgeTree.Models.Categorization.UserSkillState class, which also inherits from Brainix.GoalsCommon.KnowledgeTree.Models.Skill. This contains a UserId property and a KnowledgePoints property and is used for describing the learning state of a specific student for a skill.

To classify tasks within Brainix using the knowledge tree, the Brainix.GoalsCommon.KnowledgeTree.Models.Categorization class is used. This contains an enum CompetenceLevel and an array of Brainix.GoalsCommon.KnowledgeTree.Models.Categorization.SkillInstance. Within this object, the following rules apply:

  1. the SkillInstance array must not be empty
  2. the sum of the weights of the skillinstances within the array must equal 1 (tolerance: 0.01)
  3. no skills may be duplicated.
  4. the skills must be declared in Brainix.GoalsCommon.KnowledgeTree.KnowledgeTreeContent.
  5. the skills must all come from the same curriculum.

An example initialisation of the object can look like this:

var categorization = new Categorization 
{
	CompetenceLevel = CompetenceLevel.UnknownApplication,
	SkillInstances = new SkillInstance[] {
		new SkillInstance {
			SkillId = new Guid("51352fc7-1bf8-45d0-92cb-81f4df545bec"),
			Weight = 0.5,
		},
		new SkillInstance {
			SkillId = new Guid("1aa2d3f7-cec7-4546-a213-291193b8b6e6"),
			Weight = 0.5,
		},
	},
}

KnowledgeTreeContent

The KnowledgeTreeContent consists of a static, initialised array of curricula. Based on the structure of the learning tree explained above, all skills are listed here in categories and the corresponding curricula. This array is the basis of the learning tree and is also used as the validation basis for all categorisations. In addition, the class contains two methods that are useful for interacting with the learning tree.

Note: Don't change the content of the Knowledgetree if you are not authorised!

Unittests

For the learning tree, some unit tests are implemented in Brainix.GoalsCommon.Test.KnowledgeTree.KnowledgeTreeTests. The ValidateKnowledgeTreeSnapshot() test is particularly important here. It compares whether the current array with the curricula from the Brainix.GoalsCommon.KnowledgeTree.KnowledgeTreeContent is identical to a snapshot stored in Brainix.GoalsCommon.Test/KnowledgeTree/data/knowledgetreesnapshot.json. As soon as you edit the array with the curricula and add, remove or edit a skill, this unittest will fail. If this happens, you will not be able to upload a new version of the package to NuGet. In order for the unittest to work again, the snapshot must be manually updated first.

Refresh Snapshot

Every time the ValidateKnowledgeTreeSnapshot() test is executed, a current snapshot of the learning tree is created and saved as a file in Brainix.GoalsCommon.Test/bin/Debug/net5.0/KnowledgeTree/data/knowledgetreesnapshot.json. If one wants to replace the current snapshot with the freshly generated snapshot, the following command must be executed in the console at the level of the project:

cp .\Brainix.GoalsCommon.Test\bin\Debug\net5.0\KnowledgeTree\data\knowledgetreesnapshot.json .\Brainix.GoalsCommon.Test\KnowledgeTree\data\knowledgetreesnapshot.json

The test will then work again.

Note: Each time a new snapshot is created, it must also be inserted in the ConfigTool.

Product Compatible and additional computed target framework versions.
.NET net5.0 is compatible.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.2-beta-311 211 12/9/2022
1.0.2-beta-307 236 12/6/2022
1.0.2-beta-305 159 12/5/2022
1.0.2-beta-303 136 12/5/2022
1.0.2-beta-300 147 11/30/2022
1.0.2-beta-298 148 11/30/2022
1.0.2-beta-296 153 11/30/2022
1.0.2-beta-292 274 11/16/2022
1.0.2-beta-290 138 11/16/2022
1.0.2-beta-288 150 11/16/2022
1.0.2-beta-286 153 11/16/2022
1.0.2-beta-283 403 10/19/2022
1.0.2-beta-282 178 10/18/2022
1.0.2-beta-280 183 10/18/2022
1.0.2-beta-279 160 10/18/2022
1.0.2-beta-277 190 10/14/2022
1.0.2-beta-270 2,027 4/19/2022
1.0.2-beta-269 198 4/19/2022
1.0.2-beta-267 204 4/19/2022
1.0.2-beta-264 287 4/18/2022
1.0.2-beta-261 194 4/15/2022
1.0.2-beta-258 207 4/15/2022
1.0.2-beta-255 206 4/12/2022
1.0.2-beta-253 260 4/5/2022
1.0.2-beta-247 203 3/28/2022
1.0.2-beta-244 297 3/16/2022
1.0.2-beta-241 189 3/13/2022
1.0.2-beta-234 462 3/9/2022
1.0.2-beta-232 390 3/8/2022
1.0.2-beta-230 198 3/8/2022
1.0.2-beta-228 188 3/8/2022
1.0.2-beta-226 203 3/8/2022
1.0.2-beta-224 199 3/8/2022
1.0.2-beta-221 199 3/8/2022
1.0.2-beta-217 215 3/8/2022
1.0.2-beta-214 213 3/6/2022
1.0.2-beta-212 340 3/2/2022
1.0.2-beta-209 208 3/2/2022
1.0.2-beta-208 764 3/2/2022
1.0.2-beta-203 264 3/1/2022
1.0.2-beta-200 588 2/28/2022
1.0.2-beta-198 206 2/27/2022
1.0.2-beta-195 211 2/26/2022
1.0.2-beta-193 200 2/26/2022
1.0.2-beta-191 203 2/26/2022
1.0.2-beta-184 497 2/11/2022
1.0.2-beta-181 231 2/9/2022
1.0.2-beta-178 457 2/5/2022
1.0.2-beta-175 195 2/5/2022
1.0.2-beta-173 187 2/5/2022
1.0.2-beta-171 270 2/4/2022
1.0.2-beta-169 214 2/3/2022
1.0.2-beta-165 331 1/31/2022
1.0.2-beta-162 221 1/31/2022
1.0.2-beta-159 244 1/29/2022
1.0.2-beta-154 511 1/25/2022
1.0.2-beta-152 208 1/25/2022
1.0.2-beta-147 320 1/24/2022
1.0.2-beta-145 233 1/24/2022
1.0.2-beta-141 231 1/21/2022
1.0.2-beta-139 219 1/20/2022
1.0.2-beta-132 215 1/19/2022
1.0.2-beta-125 375 1/18/2022
1.0.2-beta-000 218 1/19/2022
1.0.1 541 1/18/2022
1.0.1-beta-120 201 1/18/2022
1.0.0-beta-99 214 1/17/2022
1.0.0-beta-96 203 1/16/2022
1.0.0-beta-94 210 1/16/2022
1.0.0-beta-92 216 1/16/2022
1.0.0-beta-9 216 1/6/2022
1.0.0-beta-88 271 1/15/2022
1.0.0-beta-85 232 1/14/2022
1.0.0-beta-82 207 1/14/2022
1.0.0-beta-80 196 1/14/2022
1.0.0-beta-78 218 1/14/2022
1.0.0-beta-75 229 1/11/2022
1.0.0-beta-73 273 1/11/2022
1.0.0-beta-71 206 1/11/2022
1.0.0-beta-7 261 1/6/2022
1.0.0-beta-69 224 1/11/2022
1.0.0-beta-67 215 1/11/2022
1.0.0-beta-65 203 1/11/2022
1.0.0-beta-63 269 1/11/2022
1.0.0-beta-61 196 1/11/2022
1.0.0-beta-58 201 1/11/2022
1.0.0-beta-56 208 1/11/2022
1.0.0-beta-53 227 1/10/2022
1.0.0-beta-51 221 1/10/2022
1.0.0-beta-50 219 1/10/2022
1.0.0-beta-48 214 1/10/2022
1.0.0-beta-46 220 1/10/2022
1.0.0-beta-44 207 1/10/2022
1.0.0-beta-42 292 1/8/2022
1.0.0-beta-40 220 1/8/2022
1.0.0-beta-4 207 1/6/2022
1.0.0-beta-38 226 1/8/2022
1.0.0-beta-36 211 1/8/2022
1.0.0-beta-35 199 1/8/2022
1.0.0-beta-32 215 1/8/2022
1.0.0-beta-26 211 1/8/2022
1.0.0-beta-24 216 1/8/2022
1.0.0-beta-22 201 1/8/2022
1.0.0-beta-20 218 1/8/2022
1.0.0-beta-15 219 1/8/2022
1.0.0-beta-13 213 1/7/2022
1.0.0-beta-128 204 1/19/2022
1.0.0-beta-112 201 1/18/2022
1.0.0-beta-109 202 1/18/2022
1.0.0-beta-104 217 1/17/2022