Skip to content

Commit

Permalink
Improved documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
FlorianRappl committed May 3, 2024
1 parent e1eacf2 commit 3d42c45
Show file tree
Hide file tree
Showing 10 changed files with 258 additions and 17 deletions.
4 changes: 3 additions & 1 deletion docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const sidebars = (): DefaultTheme.SidebarItem[] => [
{ text: "Micro Frontend", link: "/getting-started/server/microfrontend" },
{ text: "Publishing", link: "/getting-started/server/publishing" },
{ text: "Runtime Configuration", link: "/getting-started/server/environment" },
{ text: "MF Service", link: "/getting-started/server/mf-service" },
],
},
{
Expand All @@ -38,9 +39,10 @@ const sidebars = (): DefaultTheme.SidebarItem[] => [
{ text: "Provider Components", link: "/getting-started/spa/providers" },
{ text: "Root Component", link: "/getting-started/spa/root" },
{ text: "Parameters", link: "/getting-started/spa/parameters" },
{ text: "Runtime Configuration", link: "/getting-started/server/environment" },
{ text: "Pilet Service", link: "/getting-started/spa/pilet-service" },
{ text: "Localization", link: "/getting-started/spa/localization" },
{ text: "Runtime Configuration", link: "/getting-started/server/environment" },
{ text: "HTTP Injector", link: "/getting-started/spa/http" },
],
},
{
Expand Down
36 changes: 35 additions & 1 deletion docs/getting-started/server/environment.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,37 @@
# Environment Configuration

...
Runtime configuration works different for micro frontends. This is mostly true due to the fact that micro frontends are distributed, whereas the classic way is a configuration found at a central location such as a file or a some environment variables at a single server.

Nevertheless, at the end all configuration (like the rest of the code) has to end up in the application. Therefore, the configuration is assumed to be coming *with* the micro frontend. For this we rely on the response from the micro frontend discovery service (e.g., the Piral Cloud Feed Service) to also bring in configuration.

The Piral framework takes care of understanding the discovery service response and dispatching the delivered configuration to the micro frontend. In the micro frontend you can obtain the configuration via the `IMfService` or `IMfAppService` instances, e.g., passed into the setup lifecycle:

```cs
public class Module : IMfModule
{
public Task Setup(IMfAppService app)
{
// use: app.Meta.Config
}

public Task Teardown(IMfAppService app)
{
}

public void Configure(IServiceCollection services)
{
}
}
```

As the discovery service might set the configuration for published micro frontends, the debug issue for local development still exists. How do you get in configuration in here?

To make local development work you can add a `config.json` file to your micro frontend, which has to be copied to the output directory. This file can now contain a valid configuration as JSON:

```json
{
"foo": "bar"
}
```

This file is automatically picked up by the emulator when you start debugging your micro frontend.
88 changes: 88 additions & 0 deletions docs/getting-started/server/mf-service.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Standard Micro Frontend Service

Every pilet gets automatically a service called `IMfService` injected.

[[toc]]

## Events

You can use the `IMfService` service to emit and receive events via the standard API event bus. This is great for doing loosely-coupled MF-to-MF communication.

Example:

```razor
@inject IMfService ps
@implements IDisposable
<aside class=@_sidebarClass>
<a @onclick=@CloseSidebar style="display: inline-block; padding: 0 10px; cursor: pointer;">x</a>
</aside>
@code {
[Parameter]
public bool IsOpen { get; set; } = false;
[Parameter]
public EventCallback<bool> IsOpenChanged { get; set; }
string _sidebarClass { get => IsOpen ? "sidebar open" : "sidebar"; }
public void Dispose()
{
ps.RemoveEventListener<bool>("toggle-sidebar", ToggleSidebar);
}
protected override void OnInitialized()
{
ps.AddEventListener<bool>("toggle-sidebar", ToggleSidebar);
}
public void ToggleSidebar(bool value) => IsOpenChanged.InvokeAsync(value);
public void CloseSidebar() => ToggleSidebar(false);
}
```

Another component can now trigger this by using `ps.DispatchEvent("toggle-sidebar", false);` with an injected `@inject IMfService ps`.

## Sharing Data

The `IMfService` can be used to access some shared memory storage. The functions `TrySetData` and `TryGetData` can be used to set and get data on the shared storage.

Here's an example:

```razor
@inject IMfService ps
<button @onclick=@StoreValue>Store some value</button>
<button @onclick=@LogValue>Log current value</button>
@code {
public async void StoreValue()
{
if (ps.TrySetData<string>("myValue", "Hello World!"))
{
Console.WriteLine("Stored new value: {0}", value);
}
else
{
Console.WriteLine("Could not store value.");
}
}
public async void LogValue()
{
if (ps.TryGetData<string>("myValue", out var value))
{
Console.WriteLine("Currently stored value is: {0}", value);
}
else
{
Console.WriteLine("Currently nothing stored.");
}
}
}
```

Note that only the micro frontend that initially wrote the data is capable of updating it. Conflicts are therefore avoided by giving only a single micro frontend right access - following the first-write-claims-ownership principle.
32 changes: 31 additions & 1 deletion docs/getting-started/spa/environment.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,33 @@
# Environment Configuration

...
Runtime configuration works different for micro frontends. This is mostly true due to the fact that micro frontends are distributed, whereas the classic way is a configuration found at a central location such as a file or a some environment variables at a single server.

Nevertheless, at the end all configuration (like the rest of the code) has to end up in the application. Therefore, the configuration is assumed to be coming *with* the micro frontend. For this we rely on the response from the micro frontend discovery service (e.g., the Piral Cloud Feed Service) to also bring in configuration.

The Piral framework takes care of understanding the discovery service response and dispatching the delivered configuration to the micro frontend. In the micro frontend you can obtain the configuration via the `IConfiguration` passed into the `ConfigureServices` section:

```cs
public class Module
{
public static void Main() {}

public static void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
// Use IConfiguration
}
}
```

As the discovery service might set the configuration for published micro frontends, the debug issue for local development still exists. How do you get in configuration in here?

To make local development work you can enhance the response of the local debug server (mocking a micro frontend discovery service). With the `meta.json` file you can add additional sections to the micro frontend's meta data:

```json
{
"config": {
"foo": "bar"
}
}
```

Using this content you added a `config` section to the meta response of the local debug server returning you the provided configuration *locally*.
51 changes: 51 additions & 0 deletions docs/getting-started/spa/http.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# HTTP Interceptors

Pilets can add *global* HTTP interceptors, which will be triggered for all HTTP requests using the *global* `HttpClient`. This can be done by adding a singleton `IHttpInterceptor` to the services, e.g.:

```cs
public class Module
{
public static void Main() {}

public static void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHttpInterceptor, MyInterceptor>();
}
}
```

The interceptor itself has methods to react to a request (before sending the actual request) or response (after receiving the response).

```cs
class MyInterceptor : IHttpInterceptor
{
public Task<HttpRequestMessage> OnRequest(HttpRequestMessage request, CancellationToken cancellationToken)
{
// Add some header
request.Headers.Add("x-foo-bar", "other");
return Task.FromResult(request);
}

public Task<HttpResponseMessage> OnResponse(HttpResponseMessage response, CancellationToken cancellationToken)
{
// Don't do anything
return Task.FromResult(response);
}
}
```

In case you want to intercept calls for injecting a bearer token obtained from calling the `getAccessCode()` pilet API you can just add a simple convenience service:

```cs
public class Module
{
public static void Main() {}

public static void ConfigureServices(IServiceCollection services)
{
services.AddAccessCodeInterceptor();
}
}
```

This way the current access code is retrieved and inserted into the request via the `Authorization` header.
14 changes: 14 additions & 0 deletions docs/getting-started/spa/microfrontend.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,17 @@ In this case, follow these steps:
5. Build the project. The first time you do this, this can take some time as it will fully scaffold the pilet.

If you run the solution using `F5` the `Piral.Blazor.DevServer` will start the Piral CLI under the hood. This allows you to not only use .NET Hot-Reload, but also replace the pilets on demand.

## Global vs Local Pilet

By default, a pilet is treated as an isolated assembly. This makes sense, as it potentially was also developed in isolation and should not conflict with another micro frontend. Nevertheless, there are cases where you potentially want to break this isolation, e.g.:

- When providing some services that can be used from other pilets
- When manipulating global settings or configurations
- When sharing dependencies that other pilets should not have to load on their own

For these cases (and others) another type of pilet has been introduced: *global* pilets.

While *local* pilets will run in their own context, loading their own dependencies, and specifying their own services, *global* pilets are run in the default context - sharing their dependencies with the rest of the application and introducing services for all pilets.

Global pilets are also built without optimizations - this is done to prevent issues that might occur when shared dependencies are trimmed and therefore no longer working as anticipated by other pilets.
2 changes: 1 addition & 1 deletion src/Piral.Blazor.Orchestrator/MicrofrontendPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ sealed class RelatedMfAppService(string name, string version, JsonObject? config
{
private readonly IEvents _events = events;
private readonly IData _data = data;
private readonly MfDetails _meta = new () { Name = name, Version = version };
private readonly MfDetails _meta = new () { Name = name, Version = version, Config = config ?? [] };

public MfDetails Meta => _meta;

Expand Down
1 change: 1 addition & 0 deletions src/Piral.Blazor.Orchestrator/ModuleContainerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public async Task<IMfModule> ConfigureModule(Assembly assembly, IMfAppService ap
{
Name = assName.Name!,
Version = assName.Version!.ToString(),
Config = app.Meta.Config,
};
var pilet = new WrappedMfService(app, meta);

Expand Down
15 changes: 15 additions & 0 deletions src/Piral.Blazor.Shared/IMfModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,26 @@

namespace Piral.Blazor.Shared;

/// <summary>
/// Indicates the entry module for a micro frontend.
/// </summary>
public interface IMfModule
{
/// <summary>
/// The setup method that is called to wire up the micro frontend.
/// </summary>
/// <param name="app">The API to wire up the components.</param>
Task Setup(IMfAppService app);

/// <summary>
/// The teardown method that is called when disposing the micro frontend.
/// </summary>
/// <param name="app">The API to tear down the components.</param>
Task Teardown(IMfAppService app);

/// <summary>
/// Configures the local DI of the micro frontend.
/// </summary>
/// <param name="services">The service collection to use.</param>
void Configure(IServiceCollection services);
}
32 changes: 19 additions & 13 deletions src/Piral.Blazor.Shared/MfDetails.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
namespace Piral.Blazor.Shared
using System.Text.Json.Nodes;

namespace Piral.Blazor.Shared;

/// <summary>
/// Contains details about a micro frontend.
/// </summary>
public record MfDetails
{
/// <summary>
/// Contains details about a micro frontend.
/// Gets the name of the micro frontend.
/// </summary>
public record MfDetails
{
/// <summary>
/// Gets the name of the micro frontend.
/// </summary>
public required string Name { get; init; }
public required string Name { get; init; }

/// <summary>
/// Gets the version of the micro frontend.
/// </summary>
public required string Version { get; init; }
}
/// <summary>
/// Gets the version of the micro frontend.
/// </summary>
public required string Version { get; init; }

/// <summary>
/// Gets the configuration of the micro frontend.
/// </summary>
public required JsonObject Config { get; init; }
}

0 comments on commit 3d42c45

Please sign in to comment.