Skip to content
quyvu01Public

About

Distributed data mapping for .NET — declare where a property comes from, and FxMap fetches it from the service that owns the data, over the transport you already run.

Topics

Resources

Code of conduct

Contributing

Stars

12 stars

Watchers

0 watching

Forks

Repository files navigation

FxMap

FxMap

Distributed data mapping for .NET — declare where a property comes from, and FxMap fetches it from the service that owns the data, over the transport you already run.

NuGet Downloads Build License .NET

Documentation · Getting Started · Expression Language · Discord


FxMap fills the UserName and UserEmail of an order from the User service in one call

In a microservice system a response often carries only keys: an order has a UserId, a ProvinceId, a ProductId. Showing it means collecting UserName, ProvinceName, ProductName from the services that own them, and writing that glue again for every endpoint. FxMap turns the glue into a declaration:

public class OrderResponse
{
    public string UserId { get; set; }
    public string UserName { get; set; }     // filled by FxMap
    public string UserEmail { get; set; }    // filled by FxMap
}

public class OrderResponseProfile : ProfileOf<OrderResponse>
{
    protected override void Configure() =>
        UseDistributedKey<UserDistributedKey>()
            .Of(x => x.UserId)
            .For(x => x.UserName)
            .For(x => x.UserEmail, "Email");
}

await distributedMapper.MapDataAsync(response);   // one batched request per key, however many objects

The service that owns the data registers an entity configuration and a data provider. FxMap batches the ids, sends them over a transport (or answers locally when the owner is the same service), and writes the results back. No client code per field, and no coupling between the two services beyond a key name.

To compare it with the hand-written versions, sample/ManualVsFxMap fills 100 orders one call per order, with a hand-written batch, and with FxMap, and shows the request counts and what each does when the user service is down or slow.

Also in Java and Node.js

FxMap speaks one wire format, so services written in different languages can map each other's data:

Language Library Docs
.NET 8, 9, 10 FxMap (this repo) fxmapper.net
Java 21, Spring Boot 4 jfxmap j.fxmapper.net
Node.js 20+, TypeScript nfxmap n.fxmapper.net

A .NET service can fill its DTOs from a Java or Node.js service, and the other way around. To see it run, dotnet-java-nats starts a .NET service on FxMap and EF Core and a Java service on jfxmap and JPA that map each other's data over NATS, with one script.

Features

  • FluentAPI mapping: declare the mapping with ProfileOf<T> and EntityConfigureOf<T>; no attributes on your DTOs, and property access is compiled once per type.
  • Expression language: a SQL-like DSL for navigation, filters (:where), ordering and paging (:orderBy, :offset, :limit, :first, :last), aggregations, projections and conditions (Orders:where(Status = 'Done'):sum(Total)).
  • Batched and chained: one request per distributed key and dependency level, with distinct ids, however many objects are mapped; chains such as UserId → ProvinceId → CountryId resolve level by level.
  • Data providers: Entity Framework Core and MongoDB.
  • Transports: gRPC, NATS, RabbitMQ, Kafka, Azure Service Bus and Amazon SQS, with retries and supervision.
  • GraphQL integration with HotChocolate.
  • Compile-time checks: Roslyn analyzers for misconfigured profiles and entities.
  • Observable: activities for OpenTelemetry tracing on mapping and data access.
  • Targets .NET 8, 9 and 10.

Warning

All FxMap.* packages need to have the same version.

Quick Start

dotnet add package FxMap
// 1. Configure FxMap
builder.Services.AddFxMap(cfg =>
{
    cfg.AddEntitiesFromAssemblyContaining<SomeEntityAssemblyMarker>();
    cfg.AddProfilesFromAssemblyContaining<SomeProfileAssemblyMarker>();
});

// 2. Define a distributed key
public sealed class UserDistributedKey : IDistributedKey;

// 3. Configure the entity with FluentAPI
public class UserConfig : EntityConfigureOf<User>
{
    protected override void Configure()
    {
        Id(x => x.Id);
        DefaultProperty(x => x.Name);
        UseDistributedKey<UserDistributedKey>(); // Or you want to absolute lose coupling, you can use: UseDistributedKey("UserDistributedKey")
        ExposedName(x => x.Email, "UserEmail");
    }
}

// 4. Define a profile for your DTO
public class UserResponseProfile : ProfileOf<UserResponse>
{
    protected override void Configure()
    {
        UseDistributedKey<UserDistributedKey>() // Or you want to absolute lose coupling, you can use: UseDistributedKey("UserDistributedKey")
            .Of(x => x.UserId)
            .For(x => x.UserName)
            .For(x => x.UserEmail, "Email");
    }
}

Expression Examples

public class UserResponseProfile : ProfileOf<UserResponse>
{
    protected override void Configure()
    {
        UseDistributedKey<UserDistributedKey>()
            .Of(x => x.UserId)
            // Simple property access
            .For(x => x.UserEmail, "Email")
            // Navigation properties
            .For(x => x.CountryName, "Country.Name")
            // Filtering
            .For(x => x.CompletedOrders, "Orders:where(Status = 'Done')")
            // Aggregation
            .For(x => x.TotalSpent, "Orders:sum(Total)")
            // Projection
            .For(x => x.UserDetails, "{Id, Name, Address.City as CityName}")
            // GroupBy
            .For(x => x.OrdersByStatus, "Orders:groupBy(Status).{Status, :count as Count}");
    }
}

For complete expression syntax including filters, ordering and paging, functions, aggregations, boolean functions, coalesce, ternary operators, and more, visit Expression Documentation.

Packages

Package Description .NET Documentation
Core
FxMap FxMap core: profiles, entity configs, the mapper and the expression language 8.0, 9.0, 10.0 fxmapper.net
Data providers
FxMap.EntityFrameworkCore Answers requests from an Entity Framework Core DbContext 8.0, 9.0, 10.0 README
FxMap.MongoDb Answers requests from MongoDB collections 8.0, 9.0, 10.0 README
Integrations
FxMap.HotChocolate Fills GraphQL response types through HotChocolate 8.0, 9.0, 10.0 README
Transports
FxMap.Grpc gRPC transport 8.0, 9.0, 10.0 README
FxMap.Nats NATS transport 8.0, 9.0, 10.0 README
FxMap.RabbitMq RabbitMQ transport 8.0, 9.0, 10.0 README
FxMap.Kafka Apache Kafka transport 8.0, 9.0, 10.0 README
FxMap.Azure.ServiceBus Azure Service Bus transport (Standard / Premium tiers) 8.0, 9.0, 10.0 README
FxMap.Aws.Sqs Amazon SQS transport 8.0, 9.0, 10.0 README
Tooling
FxMap.Analyzers Roslyn analyzer that validates expression strings at compile time 8.0, 9.0, 10.0 README

Performance

FxMap is built to enrich data that lives in other services, so most of a real call is spent waiting on the network. This benchmark removes the network to measure only the mapper itself: FxMap enriches DTOs from an EF Core database in the same process (no transport), compared with AutoMapper 14 ProjectTo doing a single projected query. It runs on two EF Core providers, because they behave very differently.

Scenario: each order has a customer (customer → province → country) and 3-5 items (item → product → category). Both sides produce the same OrderDto graph (the benchmark checks that the results are identical).

  • AutoMapper: db.Orders.ProjectTo<OrderDto>(config). On a relational provider this is one SQL query with joins. The InMemory provider builds no SQL at all, so there "one query" is not a join.
  • FxMap: load the DTOs with only their scalar values and keys, then IDistributedMapper.MapDataAsync(dtos) resolves customer, province, country, product and category through ProfileOf<T> and the EF Core data provider.

Relational (SQLite in-memory, real joins)

Orders AutoMapper ProjectTo FxMap (query + enrich) FxMap time vs AutoMapper FxMap memory vs AutoMapper
10 303 µs / 114 KB 1.08 ms / 587 KB 3.57× 5.13×
100 2.79 ms / 540 KB 3.29 ms / 1.5 MB 1.18× 2.71×
1,000 21.4 ms / 4.8 MB 21.3 ms / 7.7 MB 1.00× 1.59×

InMemory provider (no SQL)

Orders AutoMapper ProjectTo FxMap (query + enrich) FxMap time vs AutoMapper FxMap memory vs AutoMapper
10 181 µs / 276 KB 440 µs / 387 KB 2.44× 1.40×
100 2.25 ms / 2.2 MB 2.19 ms / 1.1 MB 0.97× 0.52×
1,000 101 ms / 21.4 MB 98.7 ms / 7.4 MB 0.98× 0.35×

BenchmarkDotNet 0.15.6, 1 launch, 3 warmups, 10 iterations, .NET 10, Apple M1 Pro. Lower is better. Single-launch numbers are noisy, especially for small batches.

How to read it:

  • Against a real joined query, FxMap is not faster than ProjectTo: it is slower for small batches, on par at about 1,000 orders, and allocates more memory. Do not pick FxMap for speed inside a single database.
  • The memory advantage in the InMemory table does not carry over to a relational provider, because there ProjectTo materializes far less than it does on InMemory.
  • The fixed cost of one scoped query per distributed key and level is why small batches are slower in relative terms.
  • FxMap's value is elsewhere: the data a DTO needs lives in another service or database, where a join is not possible. Then the cost is round trips, and FxMap batches them by key. See sample/ManualVsFxMap, where 100 orders take 100 requests with a per-order call and 1 with FxMap (a hand-written batch endpoint also takes 1).
  • This is a local, single-process comparison. These numbers say nothing about network latency.

To reproduce:

cd FxMap/test/FxMap.Benchmark
dotnet run -c Release -- --filter '*.ProjectionBenchmark.*' '*.ProjectionSqliteBenchmark.*'

Documentation

Visit fxmapper.net for:

Community

If FxMap saves you glue code, a ⭐ on GitHub helps other developers find it.

Contributing

Contributions are welcome! See CONTRIBUTING.md or visit our GitHub repository to:

  • Report issues
  • Submit pull requests
  • Request features

License

This project is licensed under the Apache-2.0 license.


About

Distributed data mapping for .NET — declare where a property comes from, and FxMap fetches it from the service that owns the data, over the transport you already run.

Topics

Resources

Code of conduct

Contributing

Stars

12 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages