Membership Service
Membership management service for GoodGo platform.
Overview
The Membership Service manages member information beyond basic IAM data. It handles:
- Member Profiles - Membership information (country code, gender, preferences)
- Membership Levels - Free, Basic, Premium tier management
- Preferences - User preferences stored as JSON
Note
Profile information like avatar, phone, address, date of birth is managed by IAM Service's UserProfile. Membership Service only stores membership-specific data.
This service receives UserId from IAM Service and stores additional membership data using the same ID for consistency.
Prerequisites
| Requirement | Version |
|---|---|
| .NET SDK | 10.0.101+ |
| Docker | 24.0+ |
| PostgreSQL | 15+ (Neon recommended) |
# Check .NET version
dotnet --version
# Should output: 10.0.xxx
Quick Start
1. Configure Environment
# Copy environment template
cp .env.example .env
# Edit with your configuration
nano .env
2. Run with Docker Compose
# From deployments/local directory
cd deployments/local
docker-compose up -d membership-service-net
# View logs
docker-compose logs -f membership-service-net
3. Run Locally
# Navigate to service directory
cd services/membership-service-net
# Restore dependencies
dotnet restore
# Build all projects
dotnet build
# Run the API
dotnet run --project src/MembershipService.API
Project Structure
membership-service-net/
├── src/
│ ├── MembershipService.API/ # Presentation Layer
│ │ ├── Controllers/
│ │ │ └── MembersController.cs # Member CRUD endpoints
│ │ ├── Application/
│ │ │ ├── Commands/ # CreateMember, UpdateProfile, ChangeLevel
│ │ │ ├── Queries/ # GetMemberById, GetMembers
│ │ │ ├── Behaviors/ # Transaction, Validation, Logging
│ │ │ └── Validations/ # FluentValidation validators
│ │ └── Program.cs # App entry point
│ │
│ ├── MembershipService.Domain/ # Domain Layer
│ │ ├── AggregatesModel/
│ │ │ └── MemberAggregate/
│ │ │ ├── Member.cs # Aggregate root
│ │ │ ├── MembershipLevel.cs # Enumeration
│ │ │ └── IMemberRepository.cs # Repository interface
│ │ ├── Events/ # Domain events
│ │ ├── Exceptions/ # Domain exceptions
│ │ └── SeedWork/ # Base classes
│ │
│ └── MembershipService.Infrastructure/
│ ├── EntityConfigurations/ # EF Core configs
│ ├── ExternalServices/ # IAM Service client
│ ├── Idempotency/ # Request deduplication
│ ├── Repositories/ # Repository implementations
│ └── MembershipServiceContext.cs # DbContext
│
├── tests/
│ ├── MembershipService.UnitTests/
│ └── MembershipService.FunctionalTests/
│
├── docs/
│ ├── en/ # English documentation
│ └── vi/ # Vietnamese documentation
│
├── Dockerfile
└── docker-compose.yml
API Endpoints
Member Management
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
GET |
/api/v1/members |
Get paginated members (with search) | Yes |
GET |
/api/v1/members/{id} |
Get member by ID | Yes |
GET |
/api/v1/members/me |
Get current user's profile | Yes |
GET |
/api/v1/members/{id}/progress |
Get member's level progress | Yes |
GET |
/api/v1/members/{id}/experience |
Get member's EXP history | Yes |
POST |
/api/v1/members |
Create new member | Yes |
POST |
/api/v1/members/{id}/experience |
Add experience points | Yes |
PUT |
/api/v1/members/{id} |
Update member profile | Yes |
Health Endpoints
| Endpoint | Purpose |
|---|---|
/health |
Full health status |
/health/live |
Liveness probe (Kubernetes) |
/health/ready |
Readiness probe (Kubernetes) |
Swagger Documentation
Access Swagger UI at: http://localhost:5002/swagger
Domain Model
Member Aggregate
public class Member : Entity, IAggregateRoot
{
public Guid UserId => Id; // Same as IAM Service UserId
public string CountryCode { get; } // ISO 3166-1 alpha-2
public string? Gender { get; } // male, female, other
public MembershipLevel MembershipLevel { get; }
public string? Preferences { get; } // JSON
public bool IsDeleted { get; } // Soft delete
public DateTime CreatedAt { get; }
public DateTime UpdatedAt { get; }
public void UpdateGender(string? gender);
public void UpdateCountryCode(string countryCode);
public void UpdatePreferences(string? preferencesJson);
public void ChangeMembershipLevel(MembershipLevel newLevel);
public void Delete();
public void Restore();
}
Note
Profile fields like
PhoneNumber,AvatarUrl,Address,DateOfBirthare managed by IAM Service's UserProfile and are not part of this Member entity.
Membership Levels
| Level | ID | Description |
|---|---|---|
| Free | 1 | Default free tier |
| Basic | 2 | Basic paid membership |
| Premium | 3 | Premium membership |
Domain Events
MemberCreatedDomainEvent- When new member is createdMemberUpdatedDomainEvent- When profile is updatedMembershipLevelChangedDomainEvent- When level changes
CQRS Pattern
Commands
// Create member
public class CreateMemberCommand : IRequest<CreateMemberResult>
{
public Guid UserId { get; set; }
public string CountryCode { get; set; } = "VN";
public string? Gender { get; set; }
}
// Update profile
public class UpdateMemberProfileCommand : IRequest<UpdateMemberProfileResult>
{
public Guid MemberId { get; set; }
public string? Gender { get; set; }
public string? CountryCode { get; set; }
public string? Preferences { get; set; }
}
// Change membership level
public class ChangeMembershipLevelCommand : IRequest<ChangeMembershipLevelResult>
{
public Guid MemberId { get; set; }
public int NewLevelId { get; set; }
}
Queries
// Get by ID
public class GetMemberByIdQuery : IRequest<MemberDto?>
{
public Guid MemberId { get; set; }
}
// Get paginated list with search
public class GetMembersQuery : IRequest<GetMembersResult>
{
public int PageIndex { get; set; } = 0;
public int PageSize { get; set; } = 10;
public string? SearchTerm { get; set; }
}
Testing
# Run all tests
dotnet test
# Run unit tests only
dotnet test tests/MembershipService.UnitTests
# Run functional tests only
dotnet test tests/MembershipService.FunctionalTests
# Run with coverage
dotnet test /p:CollectCoverage=true
Test Summary:
- Unit Tests: 12 tests (Domain Member + MembershipLevel)
- Functional Tests: 3 tests (Controller authorization)
Configuration
Environment Variables
| Variable | Description | Default |
|---|---|---|
ASPNETCORE_ENVIRONMENT |
Environment name | Development |
DATABASE_URL |
PostgreSQL connection string | - |
Jwt__Authority |
JWT Authority URL | - |
Jwt__Audience |
JWT Audience | - |
appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Database=membership;Username=postgres;Password=postgres"
},
"Jwt": {
"Authority": "https://iam.goodgo.com",
"Audience": "membership-api"
},
"IamService": {
"BaseUrl": "http://iam-service-net:8080",
"TimeoutSeconds": 30,
"CacheDurationSeconds": 300
}
}
Database
Run Migrations
cd services/membership-service-net
# Create new migration
dotnet ef migrations add InitialCreate -p src/MembershipService.Infrastructure -s src/MembershipService.API
# Apply migrations
dotnet ef database update -p src/MembershipService.Infrastructure -s src/MembershipService.API
Database Schema
-- Members table
CREATE TABLE members (
id UUID PRIMARY KEY,
country_code VARCHAR(2) NOT NULL DEFAULT 'VN',
gender VARCHAR(10),
membership_level_id INT NOT NULL DEFAULT 1,
preferences JSONB,
is_deleted BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL
);
-- Indexes
CREATE INDEX ix_members_membership_level ON members(membership_level_id);
CREATE INDEX ix_members_created_at ON members(created_at);
CREATE INDEX ix_members_is_deleted ON members(is_deleted);
Note
Fields like
phone_number,avatar_url,addressare not in this schema. They are stored in IAM Service's UserProfile.
Deployment
Docker Build
# Build Docker image
docker build -t membership-service:latest .
# Run container
docker run -p 5002:8080 --env-file .env membership-service:latest
Kubernetes
See ARCHITECTURE.md for Kubernetes deployment manifests.
Related Services
- IAM Service - Authentication, identity and UserProfile (
iam-service-net) - Storage Service - File/avatar storage (
storage-service-net) - Wallet Service - Payment and points (
wallet-service-net)
IAM Service Integration
Membership Service communicates with IAM Service via IIamServiceClient:
public interface IIamServiceClient
{
// User validation
Task<IamUserInfo?> ValidateUserAsync(string accessToken);
Task<IamUserInfo?> GetUserByIdAsync(string userId, string accessToken);
Task<bool> UserExistsAsync(string userId, string accessToken);
// Roles & Permissions
Task<IReadOnlyList<string>> GetUserRolesAsync(string userId, string accessToken);
Task<IReadOnlyList<string>> GetUserPermissionsAsync(string userId, string accessToken);
Task<bool> HasPermissionAsync(string userId, string permission, string accessToken);
Task<bool> HasRoleAsync(string userId, string role, string accessToken);
// Health check
Task<IamHealthStatus> CheckHealthAsync();
Task<bool> IsAvailableAsync();
}
Resources
License
Proprietary - GoodGo Platform