Skip to main content
Masar Eagle consists of five core microservices, each responsible for a distinct business domain. This page provides a comprehensive overview of each service’s purpose, API surface, and key features.

Service Architecture Map

1. Gateway API

Gateway API

Single entry point for all client requests using YARP reverse proxy

Responsibilities

  • Route incoming requests to appropriate backend services
  • CORS policy enforcement
  • Request/response logging for monitoring
  • Authentication middleware (JWT validation)
  • Load balancing across service instances
  • Service discovery integration

Key Features

builder.Services.AddReverseProxy()
    .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"))
    .AddServiceDiscoveryDestinationResolver()
    .AddTransforms(context => context.AddXForwarded(ForwardedTransformActions.Set));

app.MapReverseProxy();

Route Configuration

The Gateway routes requests based on path prefixes:
Path PatternTarget ServicePurpose
/connect/*IdentityOpenID Connect token endpoint
/api/auth/*IdentityOTP authentication
/api/company/*UsersCompany profile management
/api/admin/trips/*TripsAdmin trip operations
/api/notifications/*NotificationsPush notification management
The Gateway does NOT store any business logic or data—it’s purely a routing layer.

2. Identity Service

Identity Service

Authentication and authorization using OpenID Connect and OpenIddict

Responsibilities

  • OTP (One-Time Password) flow for phone-based authentication
  • Password authentication for admin and company accounts
  • JWT token issuance and validation
  • Refresh token rotation
  • JWKS (JSON Web Key Set) endpoint for public key discovery
  • Token revocation and introspection

Authentication Flows

1

Request OTP

Client sends phone number to /api/auth/send-otp
2

Verify OTP

Client submits OTP code to /connect/token with grant type urn:masareagle:otp
3

Issue Tokens

Service returns access token and refresh token
4

User Provisioning

Identity publishes UserAuthenticatedEvent to create user record
src/services/Identity/src/Identity.Web/TokenEndpoint.cs:51
private static async Task<IResult> HandleOtpGrant(
    OpenIddictRequest request,
    IOtpService otpService,
    IUserPhoneResolver phoneResolver,
    IMessageBus messageBus)
{
    var phoneNumber = request.GetParameter("phone_number")?.ToString();
    var otpCode = request.GetParameter("otp_code")?.ToString();
    var userType = request.GetParameter("user_type")?.ToString();

    var otpResult = await otpService.VerifyOtpAsync(phoneNumber, otpCode);

    // Publish event to ensure user is provisioned
    await messageBus.PublishAsync(
        new UserAuthenticatedEvent(phoneNumber, userType, phoneNumber));

    return SignInWithIdentity(userId, userType);
}
1

Submit Credentials

Client sends email and password to /connect/token with grant type password
2

Verify Credentials

Service validates credentials against Users database
3

Issue Tokens

Service returns access token and refresh token
src/services/Identity/src/Identity.Web/TokenEndpoint.cs:122
private static async Task<IResult> HandlePasswordGrant(
    OpenIddictRequest request,
    IUserCredentialVerifier credentialVerifier)
{
    var userType = request.GetParameter("user_type")?.ToString();

    return userType switch
    {
        not (UserTypes.Admin or UserTypes.Company) =>
            ForbidWithError(Errors.InvalidGrant, 
                "نوع المستخدم غير مدعوم لتسجيل الدخول بكلمة مرور"),
        _ => await VerifyCredentials(request.Username!, 
                request.Password!, userType, credentialVerifier)
    };
}
All user types can refresh their access tokens:
src/services/Identity/src/Identity.Web/TokenEndpoint.cs:152
private static async Task<IResult> HandleRefreshGrant(HttpContext context)
{
    var result = await context.AuthenticateAsync(
        OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);

    return result.Succeeded 
        ? RefreshIdentity(result.Principal!)
        : ForbidWithError(Errors.InvalidGrant, 
            "رمز التحديث غير صالح أو منتهي الصلاحية");
}

Token Configuration

src/services/Identity/src/Identity.Web/OpenIddictServerConfiguration.cs:19
options.SetTokenEndpointUris("connect/token")
       .SetRevocationEndpointUris("connect/revocation")
       .SetIntrospectionEndpointUris("connect/introspect");

options.AllowPasswordFlow()
       .AllowRefreshTokenFlow()
       .AllowCustomFlow("urn:masareagle:otp");

options.SetAccessTokenLifetime(TimeSpan.FromMinutes(jwt.AccessTokenExpiryMinutes));
options.SetRefreshTokenLifetime(TimeSpan.FromDays(30));
Access tokens expire after a configurable duration (default: minutes), while refresh tokens last 30 days.

3. Users Service

Users Service

User management for drivers, passengers, admins, companies, and vehicles

Responsibilities

  • Driver profiles: Documents, vehicle info, verification status, ratings
  • Passenger profiles: Personal info, wallet, booking history
  • Admin accounts: Email/password authentication, permissions
  • Company profiles: Fleet management, driver assignment
  • Vehicle management: Types, images, seat configurations
  • Wallet operations: Deposits, withdrawals, balance tracking
  • File storage: Profile pictures, identity documents

Database Schema

The Users service maintains these tables:
src/services/Users/Users.Api/Infrastructure/Migrations/202501070001_InitialSchema.cs:12
CreateTableWithStandardColumns("Drivers", table => table
    .WithColumn("PhoneNumber").AsString(450).NotNullable().Unique()
    .WithColumn("FullName").AsString(256).Nullable()
    .WithColumn("ProfilePictureUrl").AsString().Nullable()
    .WithColumn("IdentityDocumentUrl").AsString().Nullable()
    .WithColumn("DrivingLicenseUrl").AsString().Nullable()
    .WithColumn("IsPhoneVerified").AsBoolean().NotNullable()
    .WithColumn("IsProfileComplete").AsBoolean().NotNullable()
    .WithColumn("IsActive").AsBoolean().NotNullable()
);

CreateTableWithStandardColumns("Passengers", table => table
    .WithColumn("PhoneNumber").AsString(450).NotNullable().Unique()
    .WithColumn("FullName").AsString(256).Nullable()
    .WithColumn("Gender").AsInt32().NotNullable()
    .WithColumn("ProfilePictureUrl").AsString().Nullable()
);

CreateTableWithStandardColumns("Vehicles", table => table
    .WithColumn("Name").AsString(256).NotNullable()
    .WithColumn("Model").AsString(256).NotNullable()
    .WithColumn("VehicleTypeId").AsString(450).NotNullable()
    .WithColumn("DriverId").AsString(450).NotNullable()
);

Key Features

Background Jobs

Account purge worker for inactive accounts
src/services/Users/Users.Api/Program.cs:70
builder.Services.AddHostedService<AccountPurgeWorker>();

File Storage

Local file storage with compression for profile images and documents
builder.Services.AddResponseCompression(options =>
{
    options.EnableForHttps = true;
    options.Providers.Add<BrotliCompressionProvider>();
    options.Providers.Add<GzipCompressionProvider>();
});

Service Clients

HTTP clients for calling Trips and Notifications services
src/services/Users/Users.Api/Program.cs:72
builder.Services.AddHttpClient<TripsUsageClient>("trip", 
    client => client.BaseAddress = new Uri("https+http://trip"))
    .AddServiceDiscovery();

Wolverine Outbox

Transactional outbox for reliable messaging
src/services/Users/Users.Api/Program.cs:116
await builder.UseWolverineWithRabbitMqAsync(
    new WolverineMessagingOptions
    {
        EnablePostgresOutbox = true,
        PostgresConnectionName = Components.Database.User,
        OutboxSchema = "wolverine"
    },
    opts => opts.ListenToRabbitQueue("users-api-queue")
);

4. Trips Service

Trips Service

Trip planning, booking, and payment for individual and company trips

Responsibilities

  • Trip creation and scheduling: Departure/arrival times, stops, pricing
  • Seat booking management: Passenger reservations, seat assignments
  • Payment processing: Moyasar integration, wallet deductions, refunds
  • Trip lifecycle: Scheduled → Started → Completed → Cancelled states
  • Company bookings: Fleet trips with driver assignment
  • Public trip search: Location-based, time-based, unified search strategies
  • Background jobs: Trip reminders, automatic cancellations
  • Rating system: Driver and passenger ratings

Database Schema

src/services/Trips/Trips.Api/Infrastructure/Migrations/202501070002_CreateTripsTableOnly.cs:14
Create.Table("trips")
    .WithColumn("id").AsString(450).PrimaryKey()
    .WithColumn("from_city").AsString(450).NotNullable()
    .WithColumn("to_city").AsString(450).NotNullable()
    .WithColumn("departure_time_utc").AsDateTimeOffset().NotNullable()
    .WithColumn("arrival_time_utc").AsDateTimeOffset().Nullable()
    .WithColumn("price").AsDecimal(10, 2).NotNullable()
    .WithColumn("status").AsString(50).NotNullable()
    .WithColumn("available_seats").AsInt32().NotNullable()
    .WithColumn("total_seats").AsInt32().NotNullable()
    .WithColumn("vehicle_id").AsString(450).NotNullable()
    .WithColumn("driver_id").AsString(450).NotNullable();

Create.Table("seat_bookings")
    .WithColumn("id").AsString(450).PrimaryKey()
    .WithColumn("trip_id").AsString(450).NotNullable()
    .WithColumn("seat_number").AsInt32().NotNullable()
    .WithColumn("passenger_id").AsString(450).NotNullable()
    .WithColumn("passenger_name").AsString(256).NotNullable()
    .WithColumn("status").AsString(50).NotNullable();

Key Features

Background job scheduling for trip reminders and auto-cancellations:
src/services/Trips/Trips.Api/Program.cs:115
builder.Services.AddHangfire(config => config
    .SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
    .UseRecommendedSerializerSettings()
    .UseMemoryStorage());

builder.Services.AddHangfireServer(options =>
{
    options.Queues = ["default", "notifications"];
    options.WorkerCount = Environment.ProcessorCount * 5;
});

builder.Services.AddHostedService<HangfireStartupService>();

5. Notifications Service

Notifications Service

Push notifications via Firebase Cloud Messaging (FCM)

Responsibilities

  • Device token registration: iOS, Android device tokens
  • Push notification delivery: Firebase Cloud Messaging integration
  • Notification history: Persistent storage of sent notifications
  • Batch notifications: Multi-device sending
  • Admin notification forwarding: Copy notifications to admin dashboard
  • Read/unread tracking: Notification status management

Database Schema

src/services/Notifications/Notifications.Api/Infrastructure/Migrations/202501070024_CreateDeviceTokensTable.cs:15
Create.Table("device_tokens")
    .WithColumn("id").AsString(450).PrimaryKey()
    .WithColumn("user_id").AsString(450).NotNullable()
    .WithColumn("user_type").AsString(50).NotNullable()
    .WithColumn("device_token").AsString(500).NotNullable()
    .WithColumn("platform").AsString(50).Nullable()
    .WithColumn("app_version").AsString(20).Nullable()
    .WithColumn("is_active").AsBoolean().NotNullable();

Create.UniqueConstraint("UQ_device_tokens_user_token").OnTable("device_tokens")
    .Columns("user_id", "device_token");

Event-Driven Architecture

Notifications are triggered by events from other services:
src/services/Notifications/Notifications.Api/Handlers/NotificationHandler.cs:14
private static async Task HandleNotification(
    NotificationMessage notification,
    FirebaseNotificationService firebaseService,
    IDeviceTokenRepository deviceTokenRepository,
    AppDataConnection db,
    ILogger logger)
{
    // Fetch device tokens for recipient
    List<string> deviceTokens = await deviceTokenRepository
        .GetActiveTokensAsync(notification.RecipientId, cancellationToken);

    // Send to Firebase
    int successCount = await firebaseService.SendBatchAsync(
        deviceTokens,
        notification.Title,
        notification.Body,
        notification.Data,
        cancellationToken);

    // Save to notification history
    await SaveNotificationToHistory(notification, db, cancellationToken);

    // Forward to admin if applicable
    if (shouldSendToAdmin)
    {
        await SendNotificationToAdminAsync(...);
    }
}

Wolverine Configuration

src/services/Notifications/Notifications.Api/Program.cs:74
await builder.UseWolverineWithRabbitMqAsync(opts =>
{
    opts.ListenToRabbitQueue(Components.RabbitMQConfig.QueueName,
        cfg => cfg.BindExchange(Components.RabbitMQConfig.ExchangeName));
    opts.ApplicationAssembly = typeof(Program).Assembly;
});
Notifications service is a pure consumer—it only listens to events and doesn’t publish any.

Service Communication Matrix

ServiceIdentityUsersTripsNotifications
Identity-Validates credentials (HTTP)--
UsersPublishes auth events-Queries trip usage (HTTP)Sends notifications (Event)
Trips-Queries user data (HTTP), Updates ratings (Event)-Sends trip events (Event)
Notifications-Queries user info (HTTP)--

Service Health and Monitoring

All services expose health check endpoints:
builder.Services.AddHealthChecks();

app.MapDefaultEndpoints();  // Adds /health, /alive, /ready
Health checks are monitored by:
  • .NET Aspire Dashboard: Real-time service status
  • Prometheus: Metrics scraping
  • Grafana: Visualization and alerts

Microservices Architecture

Overall architecture patterns and principles

Authentication

Deep dive into Identity service flows

Messaging

RabbitMQ and Wolverine event patterns

Database Schema

Detailed schema for each service

Build docs developers (and LLMs) love