1 - The Vision

Learn about the Dolittle vision

Dolittle Vision

Our vision at Dolittle is to build a platform to solve problems for line-of-business applications that is easy to use, increases developer productivity while remaining easy to maintain.

While our vision remains constant details around what needs to be implemented shifts over time as we learn more and gain experience on how the Dolittle framework is used in production. Dolittle will adapt as new techniques and technologies emerge.

Background

Dolittle targets the line of business type of application development. In this space there are very often requirements that are somewhat different than making other types of applications. Unlike creating a web site with content, line of business applications has more advanced business logic and rules associated with it. In addition, most line of business applications tend to live for a long time once they are being used by users. Big rewrites are often not an option, as it involves a lot of work to capture existing features and domain logic in a new implementation. This means that one needs to think more about the maintainability of the product. In addition to this, in a fast moving world, code needs to built in a way that allows for rapidly adapting to new requirements. It truly can be a life/death situation for a company if the company is not able to adapt to market changes, competitors or users wanting new features. Traditional techniques for building software have issues related to this. N-tier architecture tends to mix concerns and responsibilities and thus leading to software that is hard to maintain. According to Fred Brooks and “The Mythical Man-Month”, 90% of the cost related to a typical system arise in the maintenance phase. This means that we should aim towards building our systems in a way that makes the maintenance phase as easy as possible.

The goal of Dolittle is to help make this better by focusing on bringing together good software patterns and practices, and sticking to them without compromise. Dolittle embraces a set of practices described in this article and aims to adhere to them fully.

History

The project got started by Einar Ingebrigtsen in late 2008 with the first public commits going out to Codeplex in early 2009. It was originally called Bifrost. Source control History between 2009 and 2012 still sits there. The initial thoughts behind the project was to encapsulate commonly used building blocks. In 2009, Michael Smith and Einar took the project in a completely different direction after real world experience with traditional n-tier architecture and the discovery of commands. In 2012 it was moved to GitHub.

The original Bifrost repository can be found here.

From the beginning the project evolved through the needs we saw when consulting for different companies. Amongst these were Komplett. It has always had a high focus on delivering the building blocks to be able to deliver the true business value. This has been possible by engaging very close with domain experts and developers working on line of business solutions.

A presentation @ NDC 2011 showcases the work that was done, you can find it here. From 2012 to 2015 it got further developed @ Statoil and their needs for a critical LOB application; ProCoSys. In 2015, Børge Nordli became the primary Dolittle resource @ Statoil and late 2015 he started maintaining a fork that was used by the project. Pull Requests from the fork has been coming in steadily.

The effort of design and thoughtwork going into the project is a result of great collaboration over the years. Not only by the primary maintainers; Michael, Børge and Einar - but all colleagues and other contributors to the project.

2 - Code of conduct

Learn about what is expected from you on conduct

Contributor Covenant Code of Conduct

Our Pledge

In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.

Our Standards

Examples of behavior that contributes to creating a positive environment include:

  • Using welcoming and inclusive language
  • Being respectful of differing viewpoints and experiences
  • Gracefully accepting constructive criticism
  • Focusing on what is best for the community
  • Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

  • The use of sexualized language or imagery and unwelcome sexual attention or advances
  • Trolling, insulting/derogatory comments, and personal or political attacks
  • Public or private harassment
  • Publishing others' private information, such as a physical or electronic address, without explicit permission
  • Other conduct which could reasonably be considered inappropriate in a professional setting

Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.

Scope

This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.

Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at einar@dolittle.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project’s leadership.

Attribution

This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at http://contributor-covenant.org/version/1/4

3 - How to Contribute

Learn about how to contribute

You can contribute through the issues on any of the repositories in all the organizations found listed here. If you want to contribute with code, you can submit a pull request with your changes. Before contributing with code, it is highly recommended to read through all of our documentation here to see what we’re expecting from you as a contributor.

This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community. Read our Code of Conduct.

Contributions can also be done through documentation, all of our repositories have a Documentation folder and more details on writing documentation can be found here.

4 - Core values

Learn about what we at Dolittle believe in

At Dolittle we believe that good software stems from a set of core values. These values guides us towards our core principles and also manifested in our development principles that translates it into guidelines we use for our development. This page describes these core values to help put ourselves into the pit of success.

Privacy

We value privacy at all levels. Core to everything we do is rooted in this. This means we will always strive towards making the right technology choice that lets the owner of data have full control over where it is stored and the ownership is always very clear. These things should always be at the back every developers mind when making choices. It is easy to forget that even a little log statement could violate this.

Empowering developers

The Dolittle mission is to empower developers to create great, sustainable, maintainable software so that they can make their users feel like heroes. This is part of our DNA - representing how we think and how we approach every aspect of our product development. Our products range from libraries to frameworks to tooling, and every step of the way we try to make it as easy as possible for the developer consuming our technology.

Delivering business value

When empowering developers, this is because we want to make it easier to create great technical solutions without having to implement all the nitty and gritty details of doing so; so that our end-users - the developers we are building for, can focus on delivering the business value for their businesses. For Dolittle the developer using our technology is our end-users and represent our business value. Our promise in this is that we will build relevant technology and not technology for the technology sake. Obviously, this is balanced with innovation and we will try out things, but close the feedback loop as tight as possible so that we try things out and iterate on it to see if things are delivering business value.

User focused

At the end of the day, whatever we are building and at any level - we build things that affect an end user. There is always a person at the end of everything being done. This is critical to remember. We build software to help people build software that are more relevant and improves the lives of the actual end user using the software.

With this in hand, we work hard to understand the persona; who are we building for and what is the effect of building something.

Embracing change

The world is constantly changing, so should software. Adapting to new knowledge, new opportunities and new challenges is how the world has always moved on. It is therefor a vital piece of Dolittle to be able to embrace this change. This is a mindset and something we strongly believe in, and is also something we stribe towards in our codebase; making it possible to adapt and change to new requirements without having to recreate everything.

Being pragmatic

Pragmatism is important, keeping things real, relevant and practical is at the core of this. However, it should be treated as a trumph card for taking shortcuts and not adhering to our principles - “as that would be the pragmatic way”. It is related to the approach, which tool and in general the how. We also keep our focus on the outcome and never deviate from what outcome we are trying to achieve.

5 - Core principles

Learn about the core principles of Dolittle

Security

From everything we do; security is at the heart. We want users to feel secure when using systems built on top of the Dolittle frameworks and platform. Zero trust is way of thinking that basically ensures that all data and resources are accessed in a secure manner.

Storage over compute

For everything we do at Dolittle and in the Dolittle frameworks, we always favor using more storage than compute. Compute-power is always the most expensive part of systems while storage is the cheapest. This means if one has the chance and it sustainable - duplicates in storage for different purposes is always preferred. Since the Dolittle architecture is built around events and the source of truth is sitting inside an event store, there is a great opportunity of leveraging the storage capabilities out there and really not be afraid of duplicates. This do however mean one needs to embrace the concept of eventual consistency.

Multi-tenancy

Since compute is the most expensive, the Dolittle frameworks and platform has been built from the ground up with multi-tenancy in mind. This basically means that a single process running the Dolittle runtime, can represent multiple tenants of the application the runtime represents. This makes for a more optimal use of resources. The way one then does things is based on the execution context with the tenant information in it, we can use the correct connection-string for a database for instance or other information to a resource.

Tenant segregation

With everything being multi-tenant we also focus on segregating the tenants. This principle means that we do not share a resource - unless it can cryptographically guarantee that data could not be shared between two tenants by accident. Everything in the Dolittle frameworks has been built from the ground up with this in mind and with the resource system at play, you’ll be able to transparently work as if it was a single tenant solution and the Dolittle frameworks in conjunction with the platform would then guarantee the correct resource.

Privacy

Data should in no way made available to arbitrary personnel. Only if the data owner has consented should one get access to data. Much like GDPR is saying for personal data and the consent framework defined, business to business should be treated in the same way. This means that developers trying to hunt down a bug, shouldn’t just be granted access to a production system and its data without the consent of the actual data owner. An application developer that builds a multi-tenant application might not even be the data owner, while its customers probably are. This should be governed in agreements between the application owner and the data owner.

Just enough software

A very core value we have at Dolittle is to not deliver more than just enough. We know the least at the beginning of a project and the only way we can know if anything works is to put it into the hands of others. Only then can we really see what worked and what didn’t. Therefor it is essential that we only do just enough. In the words of Sarah Lewis; “We thrive not when we’ve done it all, but when we still have more to do” (See her TED talk here. Others has said similar things with the same sentiments - like LinkedIns Reid Hoffman said; “If you’re not embarrassed by your first product release, you’ve released it too late.”.

In order to be able to do so and guarantee a consistent level og quality, you have to have some core values and guiding principles to help you along the way. We have come up with a set of principles to make it easier to do so, read more here.

6 - Development principles

Learn about the development principles of Dolittle

We at Dolittle believe that properly crafted code will make for maintainable systems over time. Based on experience, we have found principles that helps us do just that and we’ve proven it time and time again that it truly does helps investing in this.

Consistency

One of the hardest things to accomplish is consistency, even within a single codebase. The Dolittle frameworks and platform span a number of projects and repositories and it becomes increasingly more important to stay consistent. Consistent in structure, naming, approach, principles, mindset and all. The consistency enables a high level of predictability and makes it easier to navigate for anyone using Dolittle frameworks. For anyone maintaining Dolittle frameworks, it means that its easier to navigate and change context between tasks.

High cohesion

Rather than grouping artifacts by its technical nature; keep the things that are relevant to each other close. This makes it easier to navigate and provides a more consistent structure than having to divide by technical nature. For anyone coming into a project and developing on a specific feature will have an easier time understanding and mastering that feature when its all in the same location. Examples of division by technical nature would be keep all your interfaces in an interface folder/namespace, all your frontend components in a component folder. While what you’re trying to focus on is the feature and everything related to the feature.

Cohesion is more than just at a file level within a feature, it is a mindset of keeping everything that belongs together close. Thats why we apply this for instance at a repository level as well.

High cohesion is core to the concept of a bounded context.

Divide only by the tier the artifacts belong to. See Example below.

Frontend (Web)

+-- Bounded Context 1
|   +-- Module 1
|   +---- Feature 1
|   |     | View.html
|   |     | ViewModel.js
|   |     | Styles.css
|   |     | SomeRestAPI.cs
|   |     | SomeSignalRHub.cs
|   +---- Feature 2
|   |     | View.html
|   |     | ViewModel.js
|   |     | Styles.css
|   |     | SomeRestAPI.cs
|   |     | SomeSignalRHub.cs
+-- Bounded Context 2
...

Domain

+-- Bounded Context 1
|   +-- Module 1
|   +---- Feature 1
|   |     | Command.cs
|   |     | CommandInputValidator.cs
|   |     | CommandBusinessValidator.cs
|   |     | CommandHandler.cs
|   |     | SecurityDescriptor.cs
|   |     | CommandHandler.cs
|   |     | AggregateRoot.cs
|   |     | Service.cs
|   +---- Feature 2
|   |     | Command.cs
|   |     | CommandInputValidator.cs
|   |     | CommandBusinessValidator.cs
|   |     | CommandHandler.cs
|   |     | SecurityDescriptor.cs
|   |     | CommandHandler.cs
|   |     | AggregateRoot.cs
|   |     | Service.cs
+-- Bounded Context 2
...

Event

+-- Bounded Context 1
|   +-- Module 1
|   +---- Feature 1
|   |     | Event.cs
|   +---- Feature 2
|   |     | Event.cs
+-- Bounded Context 2
...

Read

+-- Bounded Context 1
|   +-- Module 1
|   +---- Feature 1
|   |     | ReadModel.cs
|   |     | Query.cs
|   |     | QueryValidator.cs
|   |     | SecurityDescriptor.cs
|   |     | AggregateRoot.cs
|   |     | Service.cs
|   +---- Feature 2
|   |     | ReadModel.cs
|   |     | Query.cs
|   |     | QueryValidator.cs
|   |     | SecurityDescriptor.cs
|   |     | AggregateRoot.cs
|   |     | Service.cs
+-- Bounded Context 2
...

Loose coupling

Automated testing - specifications

Part of being able to move fast with precision is having a good automated test regime. One that runt fast and can be relied upon for avoiding regressions. Dolittle was built from day one with automated tests, or rather Specs - specifications. You can read more about how Dolittle does this here.

SOLID

The SOLID principles aims to make it easier to create more maintainable software. It has been the core principles at play from the beginning of Dolittle. Below is a quick summary and some relations into Dolittle.

Single Responsibility Principle

Every class should have a single responsibility, every method on this class should do only one thing. If it needs to do more things, it is most likely a coordinator and should delegate the actual responsibility to a dependency for the actual work. This is true for types and methods alike.

Open / Closed Principle

Systems and its entities should be open for extension, but closed for modification. A good examples of this is how you can extend your system quite easily by just putting in new event processor without having to change the internals of Dolittle.

Liskov Substition Principle

Objects in a program should be replaced with instances of their subtypes without altering the correctness of that program. An example of how Dolittle follows this is for instance the event store works. It has multiple implementations and the contract promises what it can do, implementations need to adhere to the contract.

Interface Segregation Principle

Interfaces should represent a single purpose, or concerns. A good example in .NET would be IEnumerable and ICollection. Where IEnumerable concerns itself around being able to enumerate items, the ICollection interface is about modifying the collection by providing support for adding and removing. A concrete implementation of both is List.

Dependency Inversion Principle

Depend on abstractions, not upon the conrete implementations. Rather than a system knowing about concrete types and taking also on the responsibility of the lifecycle of its dependencies. We can quite easily define on a constructor level the dependencies it needs and let a consumer provide the dependencies. This is often dealt with by introducing an IOC container into the system. Dolittle is built around this principle and relies on all dependencies to be provided to it. It also assumes one has a container in place, read more here.

Seperation of Concerns

Another part of breaking up the system is to identify and understand the different concerns and separate these out. An example of this is in the frontend, take a view for instance. It consists of flow, styling and logic. All these are different concerns that we can extract into their own respective files and treat independently; HTML, CSS, JavaScript. Other good examples are validation, instead of putting the validation as attributes on a model in C# - separate these into their own files like Dolittle enforces.

Read more in details about it here.

Separation of concerns

Decoupling & Microservices

At the heart of Dolittle sits the notion of decoupling. Making it possible to take a system and break it into small focused lego pieces that can be assembled together in any way one wants to. This is at the core of what is referred to as Microservices. The ability to break up the software into smaller more digestable components that makes our software in fact much easier to understand and maintain. When writing software in a decoupled manner, one gets the opportunity of composing it back together however one sees fit. You could compose it back in one application running inside a single process, or you could spread it across a cluster. It really is a deployment choice once the software is giving you this freedom. When it is broken up you get the benefit of scaling each individual piece on its own, rather than scaling the monolith equally across a number of machines. This gives a higher density, better resource utilization and ultimately better cost control. With all the principles mentioned in this article, one should be able to produce such a system and that is what Dolittle aims to help with.

Discovery

Dolittle is heavily relying on different types of discovering mechanisms. For the C# code the discovery is all about types. It relies on being able to discover concrete types, but also implementations of interfaces. Through this it can find the things it needs. You can read more about the type discovery mechanism here. It automatically knows about all the assemblies and the types in your system through the assembly discovery done at startup.

Convention over configuration

Read more about conventions here.

Cross Cutting Concerns

When concerns are seperated out, some of these can be applied cross cuttingly. Aspect-oriented programming is one way of applying these. Other ways could be more explicitly built into the code; something that Dolittle enables. The point of this is to be able to cross-cuttingly enforce code. Things that typically are repetitive tasks that a developer needs to remember to do are good candidates for this. It could also be more explicit like the security descriptors in Dolittle that enables one to declaratively set up authorization rules across namespaces for instance. This type of thinking can enable a lot of productivity and makes the code base less errorprone to things that needs to be remembered, it can be put in place one time and one can rely on it. Patterns like chain-of-responsibility can help accomplishing this without going all in on AOP.

Null

Null in code can be referred to the billion dollar mistake. You MUST at all times try to avoid using null. If you have something that is optional, don’t use null as a way to check for wether or not its provided. First of all, be explicit about what your dependencies are. A method should have overloads without the parameters that are optional. For implementations that are optional, provide a NullImplementation as the default instead. This makes program flow better and no need for dealing with exceptions such as the NullReferenceException

Runtime Exceptions

Exceptions should not be considered a way to do program flow. Exceptions should be treated as an exceptional state of the system often caused by faulty infrastructure. At times there are exceptions that are valid due to developers not using an API right. As long as it there is no way to recover an exception is fine. You should not throw an exception and let a caller of your API deal with the recovery of an exception. Exceptions MUST be considered unrecoverable.

Examples of naming of exceptions can be found in C# Coding Styles.

Immutability

Mutability in code is a challenge. For instance when dealing with threading, if an object used between two different threads is mutable, you basically have zero chance of guaranteeing its state. By making it immutable and making it explicit that you create a new version of the object when mutating - you will avoid threading issues all together. This is very core to typical functional programming languages, but is a good mindset regardless of language.

Mutability however goes even further, methods should never return a mutable type - it should protect its internals and take ownership of anything that can be mutated. That way you make your code very clear on responsibility. An example of this in C# would be returning List<>/IList<>from a method. Instead of returning this, you should be return en IEnumerable<>. A List<> would be implementing IEnumerable<>, so you don’t need to convert it to an immutable. This way the contract is saying that you can’t control its mutation and the responsibility becomes very clear. This makes responsibilities and concerns very clear.

7 - Patterns

Learn about some of the patterns we apply in Dolittle

Backend

Command Query Responsibility Segregation

Most systems has different requirements for the read and the write part of each bounded context. The requirements vary on what is needed to be written in relation to what is being read and used. The performance characteristics are also for the most part different. Most line-of-business applications tend to read a lot more than they write. CQRS talks about totally segregating the read from the write and treat them uniquely. One finds event sourcing often associated with CQRS, something that Dolittle has embraced and helps bridge the two sides and stay completely decoupled. It is an optional part of Dolittle but hightly recommended together with an event store.

Simple CQRS Diagram

Frontend

Model View View Model

MVVM is a variation of Martin Fowler’s Presentation Model. Its the most commonly used pattern in XAML based platforms such as WPF, Silverlight, UWP, Xamarin and more.

Model

The model refers to state being used typically originating from a server component such as a database. It is often referred to as the domain model. In the context of Dolittle, this would typically be the ReadModel.

View

The view represents the structure and layout on the screen. It observes the ViewModel.

ViewModel

The ViewModel holds the state; the model and also exposes behaviors that the view can utilize. In XAML the behaviors is represented by a command, something that wraps the behavior and provides a point for execution but also the ability to check wether or not it can execute. This proves very handy when you want to validate things and not be able to execute unless one is valid or is authorized. Dolittle has the concept of commands, these are slightly different however. In fact, commands in Dolittle is a part of the domain. It is the thing that describes the users intent. You can read more about them here. In the Dolittle JavaScript frontend however, the type of properties found with the XAML platforms can also be found here. Read more about the frontend commands here.

Binding

Part of connecting the View with the ViewModel and enabling it to observe it is the concept of binding. Binding sits between the View and the ViewModel and can with some implementations even understand when values change and automatically react to the change. In XAML, this is accomplished through implementing interfaces like INotifyPropertyChanged and INotifyCollectionChanged for collections.

Dolittle have full client support for both XAML based clients and also for JavaScript / Web based. For XAML and what is supported, read more in detail here. For the JavaScript support, Dolittle has been built on top of Knockout that provides obervable() and observableArray(). Read more about the JavaScript support here.

Figures

A traditional MVVM would look something like this:

MVVM Architectural Diagram

With the artifacts found in Dolittle and more separation in place with CQRS, the diagram looks slightly different

MVVM Architectural Diagram - Dolittle artifacts

You can read more details about the MVVM pattern here.

8 - Conventions

Learn about how Dolittle sees conventions

Part of a matured and maintainable solution is its conventions. All projects have this and they get established over time. The things that says that business logic goes here, this type of files goes here. The conventions established are often related to structure and it helps with consistency in your codebase.

Recipe driven development

Its not uncommon to have a Wiki with things to remember for different types of code; recipes for what you need to remember to implement for that particular type of building block. These are great candidates for automation and can also be applied cross cuttingly.

Convention over Configuration

Some systems require a lot of configuration to work and it might not even just be a thing you do at the beginning - but you have to add configuration over time as you move along. Dolittle believes that we can do a lot of this using conventions and lean on the design paradigm of convention over configuration to do so. This helps lower the number of decisions a developer has to do and as long as you stick with the conventions, it should all work out.

It also helps if you want to change the convention, as you don’t need to go change a lot of configuration in addition to changing the convention that you might have enforced in structure.

Code Conventions

We have great opportunities with modern development environments to visit the code at build time or reflect / introspect on the code at runtime. The benefits you can get from doing this are:

  • Discover artifacts in your code to avoid having to explicitly add things in code; which then makes your code adhere to the open/closed principle
  • Consistency; when things are discovered you enforce a consistency in the codebase

An example of this for frontend development is how Aurelia automatically hooks up views and view models based on the name being the same. In Dolittle we do a lot around discovering, in fact its one of the core things we do consistently.

The simplest example of a convention in play in Dolittle is during initialization, Dolittle will configure whatever IOC container you have hooked with conventions. One default convention plays a part here saying that an interface named IFoowill be bound to Foo as long as they both sit in the same namespace. You’ll see this throughout Dolittle internally as well, for instance ICommandCoordinator is bound to CommandCoordinator.

The conventions at play are described throughout the documentation when it is relevant.

9 - Domain Driven Design

Learn about Domain Driven Design and how it fits with Dolittle

Dolittle got from the beginning set to embrace Domain Driven Design and its concepts from. The reason for this is that part of modelling a system is understanding the domain that the system is targetting and understanding the vocabulary used by the domain experts in that domain and then be able to model exactly this. DDD is all about getting to a ubiquitous language that all team members use and understand.

Bounded context

In a large system you find that the system is not a single monolithic system, but rather a composition of smaller systems. Rather than modelling these together as one, bounded contexts play an important role in helping you separate the different sub systems and modelling these on their own. Putting it all together in one model tends to become hard to maintain over time and often error prone due to different requirements between the contexts that has yet to be properly defined. We see that we often have some of the same data across a system and chose to model this only once - making the model include more than what is needed for specific purposes. This leads to bringing in more data than is needed and becomes a compromise. Take for instance the usage of Object-relational mapping and a single model for the entire system approach. If you have a model with relationships and you in reality have different requirements you end up having to do a compromise of how you fetch it. For instance, if one your features displays all the parts of the model including its children; it makes sense to eagerly fetch all of this to save roundtrips. While if the same model is used in a place where only the top aggregate holds the information you need, you want to be able to lazy load it so that only the root gets loaded and not its children. The simple solution to this is to model each of the models for the different bounded contexts and use the power of the ORM to actually map to the database for the needs one has.

The core principal is to keep the different parts of your system apart and not take any dependency on any other contexts.

All the details about a bounded context should be available in a context map. The context map provides then a highlevel overview of the bounded context and its artifacts.

Building blocks

Domain Driven Design provides a set of building blocks to be able to model the domain. Dolittle aims to include most of these building blocks as long as it makes sense.

Value Object

A value object is an object that contains attributes but has no conceptual identity. They should be treated as immutable. In Dolittle you’ll find the concept value object as a good example. Value objects does not hold identity that make them unique in a system. For instance multiple persons can live on the same address, making the address a great candidate for a value object as it is not a unique identifier.

Aggregate

Aggregates represents a collection of objects that are bound together to form a root entity. In Dolittle you’ll find the AggregateRoot that represents this. Important aspect of the aggregate in Dolittle is however that it does not expose any public state, whatever entities it relies on should only be used internally to be able to perform business logic. The AggregateRootis also what is known as an EventSource.

Entity

Entities are the artifacts that aggregates can use to form the root entity. They are uniquely identified in the system. For aggregate roots in Dolittle, it is about modelling the business logic that belong together.

Repository

The repository pattern is all about providing an abstraction for working with domain objects and be storage agnostic, but focused around the needs of the domain model. Since Dolittle is built around the concept of CQRS, the domain repository is one that knows how to work with aggregate roots.

Service

When operations conceptually does not belong to the domain object, you can pull in supporting services. These are not something the aggregate knows about, but something that knows about both and coordinates it. In Dolittle this would be the CommandHandler

Domain Events

Important part of modelling the domain are the domain events. These are the things the domain experts talk about, the consequences, the things that happens in the system. Domain events represents the actual state transitions in a system. The AggregateRoot is the place where events are produced.

10 - Naming

Learn about how Doolittle’s naming conventions

One of the most important aspects of maintainable code is readability. Being able to identify what something does just by reading the name. This applies to files, type names, functions / methods - all the way through.

Abbreviations

You should not use abbreviations, unless they are well known and understood abbreviations, such as XML or JSON or similar.

Plural for modules / namespaces / folders

Typically when working on features, the feature represents an artifact in the system. This artifact is often represented as a noun in the system and the feature concerning the noun should be pluralized.

An example would be for instance Employee and the feature with everything related to this artifact would be Employees. Examples from our own code-base could be the Applications namespace, which holds Application. Similarily; ResourceTypes with ResourceType within it.

Database schemas, folders in systems or in general collections of artifacts should similarly be named like this consistently.

Prefix / postfix

Having prefixes or postfixes to type names is often considered a code-smell. It can be an indication that the name alone is not saying what it is actually doing. There is no reason to add the technical concern as a pre-/postfix. Examples of pre-/postfixes you should avoid:

  • Controller
  • ViewModel
  • Exception
  • Factory
  • Manager

Another common thing seen and done is to include the word Base as a prefix or postfix. This should not be there.

Instead of adding post/pre-fixes; make the naming unambiguous instead.

Upper CamelCase vs lower camelCase

All C# code consistently uses upper CamelCase - also called Pascal Case. While all JavaScript is consistently using lower camelCase - with the exception of types that can be instantiated. These have upper CamelCase. This last convention is a convention that is common in the JavaScript space.

Going between the two worlds, Dolittle makes sure to translate everything. During serialization for instance, translation is done for naming - both ways - making it feel natural to a C# developer as well as a JavaScript developer.

11 - Versioning

Learn about how Dolittle is versioned

Versioning

Dolittle adheres to the Semantic Versioning v2 versioning scheme.

This gives the following : <major>.<minor>.<patch>.

Patch

Patches are improvements, bug fixes and similar and is to be considered backwards compatible. This maps to the following changelog labels: Fixed, Security

Minor

Minor contains new features / functionality and is to be considered backwards compatible. This maps to the following changelog labels: Added, Deprecated

Major

Major is a breaking change - not to be considered backwards compatible. This maps to the following changelog labels: Changed, Removed

Pre-release

Pre-releases are considered an edge case and deviates the normal versioning strategy. Dolittle as a general principle does not apply this in general releases as a strategy, but might take advantage of for special cases.

Changelog

Dolittle adheres to the guidance in the Keep a change log site.

Types of changes / labels

Label  Description  Backwards compatible
Added For new features *
Changed  For changes in existing functionality -
Deprecated For soon to be removed features *
Removed For now removed features -
 Fixed For any bug fixed  *
Security  In case of vulnerabilities  *

Dependencies

Some package managers, like NuGet has a strategy of resolving to the lowest possible version it can. This means that when you have an application consuming a dependency that has a dependency to something that gets a patch, the application does not necessarily gain the benefit of this patch.

e.g.

+- Application
   +--- First level dependency
      +--- Second level dependency

When patching the second level, the first level also needs to be updated and the application itself needs to chose a wildcard dependency for either minor or patch to be able to get the patch. Dolittle recommends using a wildcard on minor and you can safely rely on the semantics of the versioning to be accurate.

Source Control

All repositories have a master branch which holds the current released software at any point in time. The branch gets tagged with the appropriate version based on each merged pull request coming in. This means that every pull request that gets merged will have a unique version number associated with it.

Issues

Issues are to be associated with every pull request (read more here). This information is used to create the changelog and versioning. Labeling of these issues is therefor vital.

Change Log Label  Issue Label Comment
Added - Implicit if not having any other type label on the issue
Changed breaking change  -
Deprecated deprecation -
Removed removal -
 Fixed bug  -
Security  security  -

Future

Dolittle is working on automating this and actually deducting the changes from the code from its public APIs. This will in time make this less error-prone.

12 - Definition of done

Learn about what we at Dolittle defines as our definition of done

We have a clear definition of what we consider to be done. These are the exit-criteria to determine if an implementation is complete. The actual coding part is only part of what actual done is. The definition of done is used actively when pull requests are reviewed.

This is our definition:

Functional software

Core to everything coming through a pull request; it must be functional software. This means it should have been tested by the developer or confirmed tested by a tester, or automated tests / specifications.

Adhering to our values and principles

It is expected that the code is adhering to our core principles and our development principles, which are well founded in our core values.

Following our expected structure

All code should be adhering to our repository structure and in general our cohesion principles as found in development principles.

Has automated specifications (tests)

We do not look at code coverage as a metric, but we look at the logical coverage. We expect our code to have automated specifications (tests) around it on a unit level. We look for behavioral specifications.

Has API documentation (XML, JsDoc, etc..)

All public APIs should have documentation around them, which is language specific and can be automatically extracted and generated API documentation for our documentation site

Has general documentation

Documentation is important to have and to maintain on changes. It is expected that any minor version bump contains the documentation for whatever is new and a major to contain the documentation for what has changed. Read more on how to contribute to documentation here.

Schemas for public formats

If exposing formats like JSON files that should be made available, create or remember to update the schema for it for it to be published. E.g. Schema Store

Ready to be deployed

Code should be ready to be deployed. Pull requests should never be made unless the code is ready to be deployed. The definition of what deployment is, is defined by each repository. For some it means deploying a package that can be consumed publicly. Which means it needs to be production ready. For other repositories, it could mean it needs to be ready to deployed to a staging environment - typically for applications being used by users.

13 - Pull requests

Learn about what we’re looking for with regards to pull requests

All of the Dolittle repositories are pull request based. That means that nothing gets into any of the repositories without it being a pull request first.

The pull request as a gated concept means that we get to do code reviews and make sure the pull request adheres to the definition of done.

Remotes and Forks

Contributers with access to a repository can send pull requests on the specific repository. For others, create your own fork and submit a pull request from the forked repository.

Delete branch after accepted pull request

After a pull request had been successfully merged, remember to delete the remote branch.

Open a draft pull request

We encourage opening a draft pull request to create a space to discuss the work being done and get feedback early in the process. It’s a lot easier to change a design that is being evolved that one that is “final”

14 - Just enough software

Learn about how we think about delivering just enough software

As described in our principles, we focus on delivering just enough software. That means we think very iteratively at a macro level and deliver just enough to make something work. We do not compromise on quality and our principles of working, but scope things down to be exactly what we need to get the feature ready to be used.

Every feature added to parts of the Dolittle platform has different development stages. For each of these stages there is a set of REQUIRED capabilities associated with it. The capabilities expected vary somewhat between the different type of features and due to the nature of the feature becomes OPTIONAL. Some things has a natural public endpoint, while others don’t.

The driving force behind defining the different stages is to get a rapid feedback loop. Deliver the bare minimum and gain experience and improve.

In no way does this mean that a feature is parked or finished, it should constantly evolve and be iterated on.

We define the stages as general:

Stage Description
0 Proof of concept - proving a piece of functionality
1 Minimum viable solution - it should work, but does not have the experience of use yet
2 Basic tooling - typically something like CLI access - if applicable
3 Advanced tooling - typically in the sense of a UI - if applicable
4 Developer tools - extensions to supported developer tools like IDEs or similar

Common

At the core of everything we do we require the following:

Specifications

All units of code should have automated specifications around them. It is not a goal of 100% coverage of code lines, but close to a 100% coverage of critical logic and the interaction between systems - which is mocked out.

Core Principles

At the heart of everything we do sits our core values, core principles and development principles that are to be considered required and prerequisites for this to work. That means we build with the values and principles at hand.

For more details on contribution, read more here. You should also read more about the vision.

Logging

Part of understanding a system is to be able to in production bubble up what it going on and follow execution paths. Logging helps with this and is a minimum requirement.

Cross Cutting

Throughout the different stages, hardening needs to be done. Once it has left the first stage and into a system and runs in production; the learning of what works and what doesn’t comes. This needs to be fed into the production immediately and takes priority.

Stage 0

Some features needs to be proven before it gets commitment from the platform. In this phase you might piggyback off of other peoples work and do the shortest path to proving the functionality you’re aiming for. A concrete example in Dolittle has been the proving of inter-bounded context communication where the first version that proved all the concepts was built on top of Kafka. While Kafka was not a viable solution for the long run.

Stage 0 is an OPTIONAL stage. Some functionality don’t need this stage as it is ready to be developed into Stage 1 directly.

Stage 1

When coming up with new functionality it is very important to gain experience from it as soon as possible. The first stage represents the MVP - Minimum Viable Product, or in our case solution. Getting it into systems that proves what works and what does not and feed the result back as soon as possible is the primary objective for this stage.

Telemetry

Some features requires the recording of telemetry. The telemetry is used by the platform to keep track of different performance indicators. This could be details like time spent processing, hit-count or similar. If the functionality being build has a natural set of performance indicators or feeds indirectly into others, it needs to use the telemetry system for this.

Telemetry is OPTIONAL and depends on the nature of the functionality being built.

API

The most important aspect of any new feature is to work on the design of the API - the surface that is being used. Getting the implementation wrong is much more forgiving than getting the design of the API surface wrong. Most effort should go into the design and the API.

API is REQUIRED. Either it is an internal API or a public API, it still is the contract and needs the most attention.

API Documentation

All code artifacts should adhere to the language specific approach to document the API. In C# for instance, it should be in the form of XML documentation.

API Documentation is REQUIRED

Public APIs - Interaction

Some features are expected to be used in Dolittle tools, be it CLI tools, All public APIs must dogfood Dolittle. This means that all APIs are represented as Commands and Queries and has a full cycle. State changes must be represented as events. A public API in Dolittle is not represented as a REST API, although that is one of the interaction layers available. A REST API is just one of many options for the different entrypoints (Commands / Queries).

Public APIs is OPTIONAL. Not all functionality needs to be interacted with and is just used internally.

Stage 2

Vital part of the success of a lot of features is the capability of interacting with it through tooling. The tools organization holds the different tools, such as the CLI; which is often a starting point for a lot of the tools.

Other tooling experience could also be small widgets in Web developer tools.

Stage 2 is OPTIONAL. Not all functionality needs to be exposed in tooling.

Stage 3

Part of the Dolittle platform is the Studio - the portal in which you have the full overview of the runtime environment and management tools to help you manage running systems built with Dolittle.

Stage 3 is OPTIONAL. Not all functionality needs to be exposed in Studio.

Stage 4

In order to make it simpler for developers, providing proper tooling experience inside code editors or IDEs - can make it a lot more accessible. The tools organization holds the different tools, including developer tools that extends these.

Stage 4 is OPTIONAL. Not all functionality needs to be exposed in Developer Tools.

15 - Repositories

Learn about how Dolittles and the repository structures

All of Dolittle repositories should be consistent in naming, structure and folder names. This gives us a higher level of consistency and it makes it easier for us to create cross cutting tools that can be applied to all of our repositories.

As part of a pull request review we look for this consistency and make sure that everything is adhering to this structure.

One of the core principles is the high cohestion principle. Keeping everything that belongs together close applies also to repositories. This is why we keep everything related to a repository within the repository and not separate on its function. An added benefit with that is that it is much easier to adhere to the definition of done.

Short names

We do not use short names for folders nor files. Examples you’ll find in other repositories and might even be considered defacto standard, are things like src and such. We believe in things being ubiquitous and have a high focus on readability. Therefor, the example above would be instead Source.

Structure

Below is the structure our repositories follow. All repositories might not have all elements, but this is what is being adhered to.

<Root of repository>
│
└─── Documentation
└─── Samples
└─── Schemas
└─── Boilerplates
└─── Source

Documentation

All the documentation, with the exception of except API documentation that is often generated from source files, must be in the Documentation folder. Follow the guide for contributing to the documentation.

All documentation is generated to our official site. Putting things in here in the excepted format and structure, it will end up eventually on the documentation site.

Samples

Samples that show concrete examples directly linked to what the repository represents, should be in the Samples folder. If there are multiple samples, these should have folders named in a way that makes it self explanatory for what they show within the Samples folder.

Schemas

If the project exposes JSON formats that one wants to have published to the Schema Store, they should be located in the Schemas folder.

BoilerPlates

Some projects has boiler plates that they use to make it easier for developers to get started. This is typically used by the Dolittle Tooling. All boiler plates should be in the BoilerPlates folder at the root of the project.

Source

All source representing the purpose of the repository, except samples, should be within the Source folder.

16 - Editor config

Learn about how your editor should be configured

In the root of all projects there SHOULD be a .editorconfig file that governs how your editor should be configured. If your editor does not support it, you need to set this up manually.

Default

All text files has this setting by default.

Property Setting
End of line LF (Unix)
Indent Spaces
Indent size 4

YAML

For YAML files, the following properties are overridden.

Property Setting
Indent size 2

17 - Logging

Learn about how you should use logging in your code

Logs are an important tool for developers to both understand program flow, and trace down bugs and errors as they appear in their software. Comprehensive, cohesive and focused log messages are key to the efficacy of logs as a development tool. To ensure that we empower developers with our software, we have put in place five guiding principles for writing log messages.

Structured log messages

Traditionally log messages have been stored as strings with data embedded with string formatting. While being simple to store and transmit, these strings loose semantic and contextual information about data types and parameters. This in turn makes searching and displaying log messages labour intensive and require specialized tools.

Modern logging frameworks support structured or semantic log messages. These frameworks split the definition of the human readable log message from the data it contains. All popular logging frameworks support the message template format specification, or a subset thereof.

logger.Trace("Committing events for {AggregateRoot} on {EventSource}", aggregateRoot.Id, eventSourceId);
TRACE 2020/04/03 12:19:58 Committing events for 9eb48567-c3ac-434b-90f1-26660723103b on 2fd8866a-9a4b-492b-8e98-791118552426
{
    "level": "trace",
    "timestamp": "2020-04-03T12:19:58.060Z",
    "category": "Dolittle.Commands.Coordination.Runtime",
    "template": "Committing events for {AggregateRoot} on {EventSource}",
    "data": {
        "AggregateRoot": "9eb48567-c3ac-434b-90f1-26660723103b",
        "EventSource": "2fd8866a-9a4b-492b-8e98-791118552426"
    }
}

Log message categories

To allow filtering of log messages from different parts of the source code during execution flow, log messages must contain a category. In most languages this category is defined by the fully qualified name of the types that define the code executed, including the package or namespace in which the type resides. These categories are commonly used during debugging to selectively enable Debug or Trace messages for parts of the software by defining filters on the log message output.

Log message levels

We define five log message levels that represent the intent or severity of the log message. They are, in decreasing order of severity:

  • Error - unrecoverable failure, resulting in end-user error.
  • Warning - recoverable failure, performance or functionality is degraded.
  • Information - information that is needed to use the software, and user activity traces.
  • Debug - execution activity and sub-activity checkpoints.
  • Trace - detailed execution trace with data that affects flow path.

Error

An error log message indicates that an unrecoverable failure has occurred, and that the current execution flow has stopped as a consequence of the failure. The current activity that the software was performing is not possible to complete, and will therefore in most cases lead to an end user error message being shown. For languages that have the concept of exceptions or errors, these must be included in an error log message. An error log message indicates that immediate action is required to recover full software functionality.

Warning

While an error message indicates an unrecoverable failure, warning log messages indicate a recoverable failure or abnormal or unexpected behavior. The current execution flow is able to continue to complete the current activity by recovering to a fail-safe state, albeit with possible degraded performance or functionality. Typical examples would be that an expected data structure that was not found but it is possible to continue with default values, or multiple data structures were found where there should only be one, but it is safe to continue. A warning log message indicates that cleanup or validation is required at a later point in time to recover or verify the intended software functionality.

Warning log messages are also used to warn developers about wrong usage of functionality, and deprecated functionality that will be removed in the future.

Information

Informational log messages tracks the general execution flow of the software, and provides the developer with required information to use the software correctly. These log messages have long term value, and typically include host startup information and user interactions with the application.

Information level log messages is typically the lowest severity messages that will be written by default, and must therefore not be used to log messages that are not useful for while the software is working as expected.

Debug

Debug log messages are used by developers to figure out where failures occur during execution flow while investigating interactively with the software. These log messages represents high-level checkpoints of activities and sub-activities during execution flow, to give hints for what log message categories and source code to investigate in more detail. Debug messages should not contain any data other than correlation and trace identifiers used to identify unique failing interactions.

Trace

Trace log messages are the most verbose of the log messages. They are used by developers to figure out what caused a failure or an unexpected behavior, and should therefore contain the data that affects the execution flow path. Typical uses of trace log messages are public methods on interface implementations, and contents of collections used for lookup.

Log output

The logs of an applications is its source of truth. It is important that log messages are consistent in where they are outputted and the format in which they are outputted. They should be outputted to a place where they can be easily retrieved by anyone who is supposed to read them. Log messages are normally outputted to the console, but they can also be appended to files. The log messages that are outputted should be readable and have a consistent style and format.

Configuring

We’re not necessarily interested in all of the logging levels or all of the categories each time we run an application. The logging should be easily configurable so that we can choose what we want to see in terms of categories and the levels of the logging. For instance software running in a production environment should consider only logging information, warning and error log messages. While we may want to show more log messages when running in development mode. It is also important to keep in mind that logging can possibly have a considerable performance cost. This is especially important to consider when deploying software with lots of logging to production environments.

Asp.Net Core

We’re using Microsoft’s logger in the Dolittle framework for .Net. We can use the ‘appsettings.json’ to configure the logging and we can provide different configurations for different environments like production and development. Look here for information on Microsoft’s logger.

Log message

Log messages should be written in a style that makes it easy to navigate and filter out irrelevant information so that we can find the cause of any error that has occurred by simply reviewing the them. Logs should be focused and comprehensive for both humans and machines. They should also be consistent in format and style across platforms, languages and frameworks.

Stick to English

There are arguably many reasons to stick to English-only log messages. One technical reason is that English ensures us that we stick to ASCII character set. This is important because we don’t necessarily know what happens to the log message. If the log messages uses specials character sets it might not render correctly or can become corrupt and thus unreadable.

Log context

Each log message should contain enough information so that the intended reader understands exactly what is going on without having to read any prior log messages. When we write log messages it is in the context of the code that we write, in the context of where the log statement is, and it is easy to forget that this context information is not implicit in the outputted log. And depending on the content of those log messages they might even not be comprehendible in the end.

There are possibly multiple aspects of ‘context’ in regards to logging. One might be the current environment or execution context of the application for when the logging is performed and the other might be domain specific context, meaning information regarding where the logging is taking place in the execution flow of an operation or values of interest like IDs and names. Log messages should have the appropriate information regarding the context relevant to the information that is intended to be communicated. For example for multi-threaded applications it would make sense to add information of the executing thread id and correlations between actions. And for multi-tenanted applications it would make sense to have information about the tenant the procedures are performed in.

It is important to consider the weight of the contextual information added to each log message. Adding lots of context information to every log message makes the log messages bloated and less human-readable. The amount of context information on a log message should be in proportion to the log message level. For instance an information log message should not contain lots of contextual information that is not strictly needed for the end-user to use the software while a trace or debug log message should contain the necessary information to deduce the cause of an error. For warning and error log messages that are produced as a result of an exception or error it is important to include the stacktrace of the exception/error as part of the log message. Usually the methods or procedures to create log messages at these levels has its own parameter for an exception/error that outputs a log with the stacktrace nicely formatted.

For statically typed languages the namespace of the code executing the logging statement is usually provided with the log message which is helpful information for the developers in the case of troubleshooting.

Keep in mind the reader of the logs

We add logs to software because someone most likely has to read them someday. Thus it makes sense that we should keep in mind the target audience when writing log messages. Which person is most likely going to read a log message affects all the aspects of that log message; The log message content, level and category is dependent on that. Information log messages is intended for the end-user while trace and debug messages are most likely only read in the case of troubleshooting, meaning that only developers will read them. The content of the log message be targeted towards the intended audience.

Don’t log sensitive information

Sensitive information like personal identifiable information, passwords and social security numbers has no place in log messages.

18 - Working Locally

Learn about working locally with Dolittle projects

Local packages

A lot of projects have a NuGet.config file, in this you’ll often find a local source and if you do a …

$ dotnet restore

… it basically fails if you don’t have the path it asks for. If you’re not interested in being able to deploy packages locally between different projects, you can add an option to ignore this:

$ dotnet restore --ignore-failed-sources

Be aware that the NuGet.config file is hierarchical in nature and sources can be disabled at any level. If you are not finding packages in the source you are expecting, check for disabled sources in any NuGet.config file. It will look like:

  <disabledPackageSources>
    <add key="local" value="true" />
  </disabledPackageSources>

Debugging locally

Be sure to read the README for DotNET.Build before starting.

If you want do debug an application into Dolittle’s source code, you have to follow these instructions:

  1. You want to make sure that when building and packing the solutions they use the locally generated packages (the ones the DeployPackagesLocally.sh script creates and copies into the right place in %HOME%/.nuget/packages)

    • This is not the case for Dolittle/DotNET.Fundamentals, since it does not have dependency on other dolittle packages.
    • For the other solutions, in the parent directory (the directory where the Build folder is present) there should be a NuGet.Config file, that file should have a reference to the local packages folder. This can be achieved by, for example, having a
    <add key="local" value="%HOME%/.nuget/packages"/>
    

    as a child of a packageSources tag in the configuration tag in the top-level Nuget.Config file. Note that when you don’t want the local v.1000 packages, this package feed source should either be disabled, or you can delete the local packages by running the DeleteLocalPackages.sh script in Build.

  2. It is really important that you deploy the packages in the right order

    1. Dolittle/DotNET.Fundamentals
    2. Dolittle/Runtime
    3. Dolittle/DotNET.SDK
    4. All other dependencies
      • Note that the other dependencies should not have dependencies on each other. If they have, then there can be trouble when creating the packages. If you’re having trouble with dependencies (assemblies not loading or similar errors at startup) then this might be the cause. Check the other dependencies if they have dependencies on each other and build and package them in the correct ordering.
  3. Make sure that the application that you want to debug also has a packageSource reference to %HOME%/.nuget/packages. Do a dotnet clean && nuget restore && dotnet restore to ensure that the solution is using the locally deployed packages.

  4. Happy debugging!

Working across multiple projects

Most Dolittle projects has a sub module for dealing with builds and adding productivity to the development experience that you can read more about here. In this there is a file called DeployPackagesLocally. Its purpose is to make it easier to work across multiple different projects that generate packages that are dependencies into higher level projects.

The way it does this is to take advantage of the NuGet option of local packages.

It has been setup with an assumed structure, between the different projects and organisations that Dolittle has. From the base path in which you have your repositories, lets assume you have a Dolittle folder and then the following structure:

+-- Dolittle
    +-- Packages (Target for NuGet packages being deployed)
    +-- DotNET.SDK
    +-- Runtime
    +-- DotNET.Fundamentals
    +-- [interaction (Organization)](https://github.com/dolittle-interaction)
    +---- AspNetCore
    +---- ... other repos
    +-- [platform (Organization)](https://github.com/dolittle-platform)
    +---- Sentry
    +---- ... other repos

As you can notice, there is a convention at play here - organizations are prefixed with dolittle-, whatever comes after the dash is then the name of the folder given. This is not important, but gives you a sense of the thinking and conventions going into this. All the repositories found in main Dolittle repository are considered “root” or core building blocks and do not belong in a sub-folder as such.

To enable a faster feedback loop you can now start deploying packages locally and be able to restore directly from these and also enable local debugging directly.

In order to do this, simply run the script from a shell:

$ ./Build/DeployPackagesLocally.sh

This script is maintained in the Build git submodule. The script will find the correct Packages folder assuming that it is in a folder that is a direct parent of the project you are deploying. If you use the conventions outlined above with the Dolittle root folder and a Packages child folder, it should work as intended.

Known Issues

When trying to develop locally using the local packages that have been built from source, you should be aware of hard-coded versions in client code. The local packages will all have the version 2.0.0-alpha2.1000. Any hard-coded version will miss this local nuget source and instead go to the dolittle nuget source and pull the appropriate version. Unfortunately, this will likely pull a whole host of other versions of the framework dlls that it relies on and lead to a “dll hell” scenario. Most likely this will manifest itself in a runtime exception of System.IO.FileLoadException, with the message “The located assembly’s manifest definition does not match the assembly reference”. Be sure to scrutinize the output of your builds and ensure that no other versions of Dolittle are being installed.

As well as hard-coded versions, you should have local versions built for all dolittle framework dlls used in your client project. For the same reason as a hard-coded version, a non local built version will not hit the local nuget cache and will pull down a different version of the framework.

When using workspaces in VSCode, be aware that things may be excluded from the Workspace that include references to other versions of the Dolittle framework. These will not be detected by search from within VSCode.

19 - Issues

Learn about how to submit issues

Dolittle has a default issue template when you create an issue in GitHub. The template is targeting bugs. For any bugs or problems, please follow the template.

Committing

When you’re committing you can reference issues with hashtag # - and the number of the issue. This will link the issue and the commit and the commit will show up as a comment on the issue. This is very useful for transparency and helps on discussing.

Branching

Creating a branch per issue is a good practice, this isolates the changes you’re doing and relates it to the issue. Name it so that it is clear which issue the branch is for; issue/# - e.g. issue/712.

Pull requests

Pull requests must associate an issue by referencing it in the title or in the description with hashtag #, as with commits.

20 - Runtime exceptions

Learn about how to work with runtime exceptions in code

Exceptions should not be considered a way to do program flow. Exceptions should be treated as an exceptional state of the system often caused by faulty infrastructure. At times there are exceptions that are valid due to developers not using an API right. As long as it there is no way to recover an exception is fine. You should not throw an exception and let a caller of your API deal with the recovery of an exception. Exceptions MUST be considered unrecoverable.

Naming of exceptions is covered by the C# Coding Styles.

21 - C# coding styles

Learn about how to write C# in Dolittle

This is the to be considered the coding standard for Dolittle and is subject to automated verification during automated builds and also part of code-reviews such as those done for pull requests. Some things are common between languages, such as naming.

Values, principles and patterns & practices

It is assumed that all code written is adhering to our core values, core principles and development principles. On top of this we apply patterns that also reflect a lot of the mindset of things we do. Read more here.

Compactness

In general, code should be compact in the sense that any “noise” of language artifacts or similar that aren’t really needed SHALL NOT be used. This to increase readability, not decrease it. Things that are implicit, SHALL be left implicit and not turned into explicits.

Keywords

Use of var

Types are implicitly provided by the compiler and considered noise during declaration. If one feel the need for explicitly declaring variables with their type, it is often a symptom of something else being wrong - such as large methods that you can’t get a feel for straight away. This is most likely breaking the Single Responsibility Principle. You MUST use var and let the compiler infer the type implicitly.

Private members

In C# the private modifier is not needed as this is the default modifier if nothing is specified. Private members SHALL NOT have a private modifier.

Example:

public class SomeClass
{
    string _someString;
}

this

Explicit use of this SHALL NOT be used. With the convention for prefixing private members, the differentiation is clear.

Prefixes and postfixes

A very common thing in naming is to include pre/post fixes that describes the technical implementation or even the pattern that is being used in the implementation. This does not serve as useful information. Examples of this is Manager, Helper, Repository, Controller and more (e.g. EmployeeRepository). You SHOULD NOT pre or postfix, but rather come up with a name that describes what it is. Take EmployeeRepositorysample, the postfix Repository is not useful for the consumer; a better name would be Employees.

Member variables

Member variables MUST be prefixed with an underscore.

Example:

public class SomeClass
{
    string _someInstanceMember;
    static string _someStaticMember;
}

One type per file

All files MUST contain only one type.

Class naming

Naming of classes SHALL be unambiguous and by name tell exactly what it is providing. Example:

// Coordinates uncommitted event streams
public class UncommittedEventStreamCoordinator {}

Interface naming

Its been a common naming strategy to include Iin front of any interface. Prefixing with Ican have other meaning as well, such as the actual word “I”. This can give better naming to interfaces and better meaning to names.

Examples:

// Implemented by types that can provide configuration
public interface ICanConfigure {}

// Implemented by a type that can provide a container instance
public interface ICanCreateContainer

You SHOULD try look for this way of naming, as it provides a whole new level of expressing intent in the code.

Private methods

Private methods MUST be placed at the end of a class.

Example:

public class SomeClass
{
    public void PublicMethod()
    {
        PrivateMethod();
    }


    void PrivateMethod()
    {

    }
}

Exceptions

flow

Exceptions are to be considered exceptional state. They MUST NOT be used to control program flow. Exceptional state is typically caused by infrastructure problems or other problems causing normal flow to be able to continue.

types

You MUST create explicit exception types and NOT use built in ones. The exception type can implement one of the standard ones.

Example:

public class SomethingIsNull : ArgumentException
{
    public SomethingIsNull() : base("Something was null") {}
}

Throwing

If there is a reason to throw an exception, your validation code and actual throwing MUST be in a separate private method.

Example:

public class SomeClass
{
    public void PublicMethod(string something)
    {
        ThrowIfSomethingIsNull(something);
    }

    void ThrowIfSomethingIsNull(string something)
    {
        if( something == null ) throw new SomethingIsNull();
    }
}

Async / Await

In C# the async / await keywords should be used with utmost care. It is a thing that without really thinking it through can bleed throughout your codebase without necessarily a good reason. Alongside async / await comes the Task type that needs to be there. The places where threading is necessary, it MUST be dealt with internally to the implementation and not bleed throughout its APIs. Dolittle has a very good handle on its entrypoints and from these entrypoints, the need for scaling out across multiple threads are rarely needed. With the underlying infrastructure being relied on, web requests are already threaded. Since we enter the system and returns back as soon possible, we have a good grip of when this is needed. Threads can easily get out of hand and actually slow down systems.

Exposing IList / ICollection

Public APIs SHALL NOT have mutable types as return types, such as IList, ICollection. The responsibility for maintaining state should be with the owner of it. By exposing the ability for changing state outside the owner, you lose control over who can change state and side-effects occur that aren’t clear. Instead you should always expose immutable types like IEnumerable instead.

Mutability

One of the biggest cause of side-effects in a system is the ability to mutate state and possibly state one does not necessarily own. The example is something creates an instance of an object and exposes public getters and setters for its properties and inviting anyone to change this state. This makes it hard to track which part of the system actually changed the state. Be very conscious about ownership of instances. Avoid mutability. Most of the time it is not needed. Instead, create new objects with the mutation in place.

22 - C# Specifications

Learn about how to write C# specifications

All the C# code has been specified by using Machine Specifications with an adapted style. Since we’re using this for specifying units as well, we have a certain structure to reflect this. The structure is reflected in the folder structure and naming of files.

Folder structure

The basic folder structure we have is:

(project to specify).Specs
    (namespace)
        for_(unit to specify)
            given
                a_(context).cs
            when_(behavior to specify).cs

A concrete sample of this would be:

Dolittle.Specs
    Commands
        for_CommandContext
            given
                a_command_context_for_a_simple_command_with_one_tracked_object.cs
            when_committing.cs

The implementation SHOULD then look something like this :

    public class when_committing : given.a_command_context_for_a_simple_command_with_one_tracked_object_with_one_uncommitted_event
    {
        static UncommittedEventStream   event_stream;

        Establish context = () => event_store_mock.Setup(e=>e.Save(Moq.It.IsAny<UncommittedEventStream>())).Callback((UncommittedEventStream s) => event_stream = s);

        Because of = () => command_context.Commit();

        It should_call_save = () => event_stream.ShouldNotBeNull();
        It should_call_save_with_the_event_in_event_stream = () => event_stream.ShouldContainOnly(uncommitted_event);
        It should_commit_aggregated_root = () => aggregated_root.CommitCalled.ShouldBeTrue();
    }

The specifications should read out very clearly in plain English, which makes the code look very different from what we do for our units. For instance we use underscore (_) as space in type names, variable names and the specification delegates. We also want to keep things as one-liners, so your Establish, Because and It statements should preferably be on one line. There are some cases were this does not make any sense, when you need to verify more complex scenarios. This also means that an It statement should be one assert. Moq is used for for handling mocking / faking of objects.

23 - Copyright header

Learn about the requirements of copyright headers in code files

Code files

All code files MUST to have the following copyright header, this includes even automated test files for all languages. The format needs to adhere to the following.

// Copyright (c) Dolittle. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

For XML based languages, this would look like:

<!-- Copyright (c) Dolittle.  All Rights Reserved.  Licensed under the MIT License. See LICENSE file in the project root for full license information. -->

Other languages might have other ways to represents comments, for instance bash/shell scripts or similar:

# Copyright (c) Dolittle. All rights reserved.
# Licensed under the MIT license. See LICENSE file in the project root for full license information.