Skip to content

Sharing Levels integration

These are the steps to integrate the Sharing Levels clients into our services. Once we finish with the integration all the domain entities being persisted will be intercepted applying the sharings algorithm and the corresponding Sharing Level will be persisted within these entities.

If you want to know more about Sharings, take a look at:

Backend-for-frontend

Note

This step can be skipped if you are working in AdminCenter, since its already implemented.

Add the following reference to your BFF application project:

XML
<ProjectReference Include="$(ServicesPath)SharingLevels\ITsynch.Suite.SharingLevels.BffClientModule\ITsynch.Suite.SharingLevels.BffClientModule.csproj" />

And add the dependency in the application module.

C#
1
2
3
4
5
6
7
8
using ITsynch.Suite.SharingLevels;

public override void SetupModule(IModuleBuilder builder)
{
    base.SetupModule(builder);

    builder.DependsOn<SharingLevelsBffClientModule>();
}

This module propagates the Sharing Levels header that we are receiving from the UI to backend services accessed via GraphQL federations.

Backend service

Add the following reference to your application project:

XML
<ProjectReference Include="$(ServicesPath)SharingLevels/ITsynch.Suite.SharingLevels.ClientModule/ITsynch.Suite.SharingLevels.ClientModule.csproj" />

Add the dependency in the application module and configure the SharingMode of all the domain entities.

C#
public override void SetupModule(IModuleBuilder builder)
{
    base.SetupModule(builder);

    builder.DependsOn<SharingLevelsClientModule, SharingLevelsClientModuleOptions>(
        opts =>
        {
            opts.SetDbContext<EntityDbContext>();
            opts.ConfigureEntity<Entity>(SharingMode.ByEntity);
            opts.ConfigureEntity<AnotherEntity>(SharingMode.ByEntity);
        });
}

Important

This sharing entity configuration only works if the entity is described. Otherwise, it will throw an exception.

There are some cases where our entities are tightly related and it makes sense that they would follow the same sharing configuration. A common use case are aggregate roots, where the root entity would be our main entity being configured, and it should be "followed" by its children.

The client module provides a specific API to accomplish this.

C#
public override void SetupModule(IModuleBuilder builder)
{
    base.SetupModule(builder);

    builder.DependsOn<SharingLevelsClientModule, SharingLevelsClientModuleOptions>(
        opts =>
        {
            opts.SetDbContext<EntityDbContext>();
            opts.ConfigureEntity<Entity, RelatedEntity>(SharingMode.ByEntity);
        });
}

Both entities will share the same configuration, but it is the main entity who "guides" it's followers (you can add more than one entity following a principal one).

Now, moving to the Domain library we need to add the reference of Abstractions:

XML
<ProjectReference Include="$(ServicesPath)SharingLevels/ITsynch.Suite.SharingLevels.Abstractions/ITsynch.Suite.SharingLevels.Abstractions.csproj" />

And implement the IShareableEntity interface to our domain entity:

C#
using ITsynch.Suite.SharingLevels;

namespace ITsynch.Suite.Entity.Domain
{
    public class Entity : IShareableEntity
    {
        /// <inheritdoc />
        public Guid SharingLevelId { get; set; }
    }
}

Also, add the same property to the following:

GraphQL Entity

C#
1
2
3
4
5
6
7
namespace ITsynch.Suite.Entity.Application.GraphQL
{
    public record Entity
    {
        public Guid SharingLevelId { get; set; }
    }
}

GraphQL Create and Update Entity

C#
1
2
3
4
5
6
7
namespace ITsynch.Suite.Entity.Application.GraphQL
{
    public record CreateEntity
    {
        public Guid SharingLevelId { get; set; }
    }
}

CreateOrUpdateEntity Message

C#
1
2
3
4
5
6
7
namespace ITsynch.Suite.Entity.Application
{
    public record CreateOrUpdateEntity
    {
        public Guid SharingLevel { get; set; }
    }
}

Remember to add the mapping of these new properties to the corresponding Automapper profile.

Update your consumer/saga:

C#
1
2
3
var entityData = context.Message;

entity.SetSharingLevel(entityData.SharingLevel);

Note

Remember to add new Migrations.

Lastly, we need to add the SharingLevels.AspNetModule. The place where we add it will depend on where you are exposing the mutations, for example : if you have mutations exposed in AdminCenter, you should add the dependency there and the other case is when you are exposing the mutations in the backend service itself (e.g: Discussions, FileStorage, etc.) then add it there.

First, add the reference:

XML
<ProjectReference Include="$(ServicesPath)SharingLevels\ITsynch.Suite.SharingLevels.AspNetModule\ITsynch.Suite.SharingLevels.AspNetModule.csproj" />

And finally add the module dependency:

C#
1
2
3
4
5
6
7
8
using ITsynch.Suite.SharingLevels;

public override void SetupModule(IModuleBuilder builder)
{
    base.SetupModule(builder);

    builder.DependsOn<SharingLevelsAspNetModule>();
}

This module will hydrate the ISuiteContext with the current SharingLevel.

UI Integration

The steps to follow to able to use sharing levels in the UI and hydrate each mutation with the current sharing level are as follows:

First of all, you need to add suiteSharingLevelsConfiguration in you application module

TypeScript
1
2
3
4
5
6
const AdminCenterConfiguration: SuiteApplicationConfiguration = {
    ...,
    suiteSharingLevelsConfiguration: {
        enabled: true
    }
};

Then, you will need to call supportSharingLevel method from the customizeForm method in you creation/edition form page effect. This method will hide the field 'SharingLevelId' in the form, but it will populate that field with the current sharing level.

TypeScript
1
2
3
4
5
this.customizeForm((b) =>
            b
                .supportSharingLevel()
                ...
        );

And that's it, with these minor changes in the UI, it will send the current sharing level on the mutation and receive it on the backend, saving that field to the database.

Testing

First, make sure you have the sharing levels test project dependency added to your test project:

XML
<ProjectReference Include="$(ServicesPath)SharingLevels\ITsynch.Suite.SharingLevels.ClientModule.Testing\ITsynch.Suite.SharingLevels.ClientModule.Testing.csproj" />

If your entity is shareable, in testing runtime it will try to apply sharings as well, so to mock these sharings (otherwise the sharings logic will fail) you need to mock Sharing Levels, relate them to your entity and configure the mock objects:

C#
private SharingLevelsStoreMock SharingLevelsStoreMock { get; }

private Guid CurrentSharingLevelId => this.SourceLevel.CorrelationId;

private SharingLevel SourceLevel { get; }

private SharingLevel TargetLevel { get; }

public EntityIntegrationTests(ITestOutputHelper helper)
    : base(helper)
{
    this.SourceLevel = new SharingLevel
    {
        CorrelationId = NewId.NextGuid(),
        DisplayName = "Source level",
        ParentId = null
    };

    this.TargetLevel = new SharingLevel
    {
        CorrelationId = NewId.NextGuid(),
        DisplayName = "Target level",
        ParentId = null
    };

    this.SharingLevelsStoreMock = SharingLevelsStoreMock.Create(entitySharingConfig =>
    {
        entitySharingConfig.ConfigureSharingForEntity<Entity>(sharingConfig =>
        {
            sharingConfig.AddSharing(this.SourceLevel, this.TargetLevel);
        });
    });
}

protected override void WireSuiteApplication(IHostBuilder hostBuilder)
{
    base.WireSuiteApplication(hostBuilder);

    var suiteContext = new Mock<ISuiteContext>();
    suiteContext.Setup(d => d.Get(It.IsAny<string>())).Returns(this.CurrentSharingLevelId.ToString());

    hostBuilder.ConfigureContainer<IServiceCollection>(services =>
    {
        services.Replace(ServiceDescriptor.Scoped(_ => suiteContext.Object));
        services.Replace(new ServiceDescriptor(
            typeof(ISharingLevelsStore),
            this.SharingLevelsStoreMock.Object));
    });
}

And if your entity has Entity Management implemented (the entity is described) you need to add the following configuration:

C#
1
2
3
4
5
6
public override async Task InitializeAsync()
{
    await base.InitializeAsync();

    this.SharingLevelsStoreMock.UseEntityManagement(this.ServiceProvider);
}

Disabling SharingLevels BusObserver

When you depend on the SharingLevelsClientModule a BusObserver will be added to DI, this can cause some issues when running tests, in order to deactivate it you can do one of the following.

If your test has a custom Suite TestModule, you can simply add the following dependency:

C#
1
2
3
4
5
6
public override void SetupModule(IModuleBuilder builder)
{
    base.SetupModule(builder);

    builder.DependsOn<SharingLevelsTestingClientModule>();
}

If that's not the case, you need to override the following method:

C#
1
2
3
4
5
6
7
8
protected override void ConfigureBootstrapServices(
    HostBuilderContext context,
    IServiceCollection bootstrapServices)
{
    base.ConfigureBootstrapServices(context, bootstrapServices);

    bootstrapServices.UseSharingLevelsTestingModule();
}

Now, the Sharing Levels client bus observer will not run.