Documentation Contribution Guidelines
Contributions don't need to be limited just to source code - contributions to documentation are also extremely helpful and assist users in understanding the Akka.NET project.
Website
This project uses DocFX to generate our website. This tool uses its own version of the Markdown language named DocFX Flavored Markdown for crafting the documents for the website. Any editor with a valid Markdown plugin based will give you the best preview/edit experience, such as Atom or StackEdit.
To contribute to the website's documentation, fork the main GitHub repository Akka.NET. The documentation is under the docs directory. Please be sure to read the CONTRIBUTING.md
before getting started to get acquainted with the project's workflow.
Organization of Documentation
In order to keep the documentation discoverable for users who are unfamiliar with the Akka.NET project, we have to enforce a degree of top-down organization to achieve this.
Our general sitemap looks like this:
If you want to contribute a new page or documentation area, this should help you generally figure out where to categorize it. If you aren't sure where you should add a new piece of documentation, ask in project chat or Akka.NET GitHub Discussions.
Moving Documentation Pages
One thing we absolutely don't tolerate is breaking existing links in our documentation as lots of external resources depend upon it. Thus, there's a procedure for moving a page from one directory to another that helps us preserve prior links.
Step 1 - Remove the old page from toc.yml
.
We need to do this in order to prevent the old page from showing up in the navigation under its previous location - we'll add the new destination page back to the toc.yml
of the appropriate directory.
Step 2 - Move the {filename}.md
file to its new location.
Move the content to where it's going to live going forward.
Step 3 - Add the moved {filename}.md
file to the toc.yml
of the new folder location.
This will update the internal navigation and search to discover the new document.
Step 4 - Add a {filename}.html
in the old location of the previous {filename}.md
file.
This file is going to contain content that looks like this:
<!DOCTYPE html>
<html>
<head>
<title>Building and Distributing Akka.NET</title>
<meta http-equiv = "refresh" content="1;url=../contributing/build-process.html" />
</head>
<body>
<p>This page has been moved to <a href="../contributing/build-process.html">Akka.NET Build Process</a>.</p>
</body>
</html>
The HTML file uses a meta http-equiv = "refresh"
tag to send the user, via an HTTP 301 redirect, to the new file location where the content has been moved. Yes, this is a pain but this is done in order to make sure that third party content and search engines can still find what they're looking for even after the content has been moved.
Note
In the future this won't be necessary. Once DocFx3 ships native support for folder and file-level redirects will be supported: https://github.com/dotnet/docfx/issues/3686
DocFx Hygiene
This section of the documentation explains the DocFx hygiene the Akka.NET project employs in order to ensure that:
- It's easy to correctly link between documents;
- To reference code samples directly from the source code of the project, so those code samples are updated automatically when they're modified in-source; and
- To make it easier to extend the documentation over a long period of time.
Code Samples Must Use !code
References
One of the biggest sources of byte rot, when it comes to documentation, is that the samples embedded in it are gradually deprecated within the code it documents and are subsequently never updated. As a result of this end-users end up newly adopted already-obsolete practices, anti-patterns, and have a bad experience trying to adopt Akka.NET or any other software library.
Thus DocFx Flavored Markdown has a great solution for us: !code
snippets.
[!code-<language>[<name>](<codepath><queryoption><queryoptionvalue> "<title>")]
These allow us to embed code directly from Akka.NET's own source code, tests, and example projects into documentation articles. This is extremely useful as it helps us ensure that when the underlying code sample gets updated the documentation articles that reference that code are subsequently updated as well.
Targeting Referenced Code With #region
So what does a real-world example of this look like? From the Akka.Cluster.Tools.ClusterClient
documentation:
[!code-csharp[Main](../../../src/core/Akka.Docs.Tests/Networking/ClusterClient/ClientListener.cs?name=ClusterClient)]
This references the following code in the Akka.Docs.Tests
project:
#region ClusterClient
public class ClientListener : UntypedActor
{
private readonly IActorRef _targetClient;
public ClientListener(IActorRef targetClient)
{
_targetClient = targetClient;
}
protected override void OnReceive(object message)
{
Context.Become(ReceiveWithContactPoints(ImmutableHashSet<ActorPath>.Empty));
}
protected override void PreStart()
{
_targetClient.Tell(SubscribeContactPoints.Instance);
}
public UntypedReceive ReceiveWithContactPoints(IImmutableSet<ActorPath> contactPoints)
{
return (message) =>
{
switch (message)
{
// Now do something with the up-to-date "cps"
case ContactPoints cp:
Context.Become(ReceiveWithContactPoints(cp.ContactPointsList));
break;
// Now do something with an up-to-date "contactPoints + cp"
case ContactPointAdded cpa:
Context.Become(ReceiveWithContactPoints(contactPoints.Add(cpa.ContactPoint)));
break;
// Now do something with an up-to-date "contactPoints - cp"
case ContactPointRemoved cpr:
Context.Become(ReceiveWithContactPoints(contactPoints.Remove(cpr.ContactPoint)));
break;
}
};
}
}
#endregion
In this case we're telling DocFx to include all of the code between the #region
and #endregion
tags for a region named ClusterClient
within the ClientListener.cs
file in this directory.
This is preferable to referencing entire files or using specific line numbers because it's concise and can still be refactored in the future without having to update the documentation.
Targeting Referenced Code With //<{name}>
If you don't want to use #region
s to target referenced code inside Akka.NET's documentation, we can also use DocFx's tag syntax to accomplish the same objective:
[!code-csharp[Main](../../../src/core/Akka.Docs.Tests/Networking/ClusterClient/ClientListener.cs?name=ClusterClient)]
Would also work if we used DocFx's "tag" syntax:
// <ClusterClient>
public class ClientListener : UntypedActor
{
private readonly IActorRef _targetClient;
public ClientListener(IActorRef targetClient)
{
_targetClient = targetClient;
}
protected override void OnReceive(object message)
{
Context.Become(ReceiveWithContactPoints(ImmutableHashSet<ActorPath>.Empty));
}
protected override void PreStart()
{
_targetClient.Tell(SubscribeContactPoints.Instance);
}
public UntypedReceive ReceiveWithContactPoints(IImmutableSet<ActorPath> contactPoints)
{
return (message) =>
{
switch (message)
{
// Now do something with the up-to-date "cps"
case ContactPoints cp:
Context.Become(ReceiveWithContactPoints(cp.ContactPointsList));
break;
// Now do something with an up-to-date "contactPoints + cp"
case ContactPointAdded cpa:
Context.Become(ReceiveWithContactPoints(contactPoints.Add(cpa.ContactPoint)));
break;
// Now do something with an up-to-date "contactPoints - cp"
case ContactPointRemoved cpr:
Context.Become(ReceiveWithContactPoints(contactPoints.Remove(cpr.ContactPoint)));
break;
}
};
}
}
// </ClusterClient>
These // <ClusterClient>
and // </ClusterClient>
tags would accomplish the exact same result as using #region
and #endregion
.
Finding Appropriate Code Samples
You are free to reference samples from any part of the Akka.NET code inside the documentation, but it's often best to have a dedicated code sample for each concept we want demonstrated in the documentation.
In that case it might be best to do one of the following:
- Contribute a new code sample to the
Akka.Docs.Tests
project - these are all unit tests that are referenced somewhere in our DocFx documentation but those tests must still pass or fail or - Add a dedicated code sample to the
src/samples
directory, in the event that it's sufficiently complex.
All Pages Must Have a uid
Defined
If you look closely at the header of each DocFx article in this repository you'll notice the following at the top of each page:
---
uid: documentation-guidelines
title: Documentation Contribution Guidelines
---
The title
defines the page's <title>
tag, but the uid
is the canonical unique identity of a given DocFx document. It allows us to build linking systems within DocFx that don't rely on fixed directory structures.
All Links Between Documents Must Use xref
So per the previous point above about all pages needing to have their own uid
s defined, all links between documents within the Akka.NET documentation should all be done using the xref
format:
[`Akka.Cluster.Tools.ClusterClient` documentation](xref:cluster-client)
This allows us to link to a document regardless of where it is in our file structure, which means that in the event that documentation content is refactored or re-organized the links will still work.
Building Documentation Locally
Akka.NET's DocFx documentation can be built locally via a clone of the main Akka.NET GitHub repository
To preview the documentation for this project, execute the following commands at the root of your local clone of the repository:
Windows
build.cmd docfx
Linux / OS X
build.sh docfx
This will generate all of the static HTML / CSS / JS files needed to render the website into the ~/docs/_site
folder in your local repository.
In order for all of the JavaScript components to work correctly in your browser, you'll need to serve the documents via a local web server rather than the file system. You can launch DocFx's build in server via the following script in the root of this repository:
serve-docs.cmd
This will use the built-in docfx.console
binary that is installed as part of the NuGet restore process from executing any of the usual build.cmd
or build.sh
steps to preview the fully-rendered documentation.
Markdown Linting
Akka.NET's build system leverages markdownlint
(via markdown-cli
) to validate formatting of the articles, headline capitalization, and lots of other details.
To run markdownlint
locally you'll want to have Node.JS installed along with Node Package Manager (npm
).
Installation
To install markdownlint-cli
execute this command to add it globally to your npm
command line:
npm install -g [email protected] [email protected]
Run To run the markdown linting rules for Akka.NET's documentation, in the root directory of the Akka.NET GitHub repository:
markdownlint "docs/**/*.md"
If there are any linting errors the exact filename, line number, and rule infraction will be listed there. This is the exact same command we run in Akka.NET's pull request validation system.
Code
When documenting code, please use the standard .NET convention of XML triple-slash documentation comments. This allows the project to use tools like Sandcastle to generate the API documentation for the project. The latest stable API documentation can be found here.
Please be mindful to including useful comments when documenting a class or method. Useful comments means including full English sentences when summarizing the code and not relying on pre-generated comments from a tool like GhostDoc. Tools like these are great in what they do if supplemented with well-reasoned grammar.
BAD obviously auto-generated comment
/// <summary>
/// Class Serializer.
/// </summary>
public abstract class Serializer
{
/// <summary>
/// Froms the binary.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <param name="type">The type.</param>
/// <returns>System.Object.</returns>
public abstract object FromBinary(byte[] bytes, Type type);
}
GOOD clear succinct comment
/// <summary>
/// A Serializer represents a bimap between an object and an array of bytes representing that object.
/// </summary>
public abstract class Serializer
{
/// <summary>
/// Deserializes a byte array into an object of type <paramref name="type"/>
/// </summary>
/// <param name="bytes">The array containing the serialized object</param>
/// <param name="type">The type of object contained in the array</param>
/// <returns>The object contained in the array</returns>
public abstract object FromBinary(byte[] bytes, Type type);
}
We've all seen the bad examples at one time or another, but rarely do we see the good examples. A nice rule of thumb is to write the comments you would want to read while perusing the API documentation.