Skip to content

Commit 3f68845

Browse files
committed
Implement multi-tenancy support across all services
- Add tenant components to booking, notification, payment, train, and user services - Create tenant tables and add tenant_id columns to all entity tables - Update entity classes and services to support multi-tenancy - Add database migrations for tenant tables and tenant_id columns - Update application configurations for multi-tenancy
1 parent fb8f4d0 commit 3f68845

72 files changed

Lines changed: 3289 additions & 25 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

MULTI_TENANCY_IMPLEMENTATION.md

Lines changed: 390 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,390 @@
1+
# Multi-Tenancy Support Implementation Guide
2+
3+
## Overview
4+
5+
Comprehensive multi-tenancy support for IRCTC microservices using the **Shared Database, Shared Schema** approach. This implementation provides tenant isolation, tenant context propagation, and tenant management capabilities.
6+
7+
## Architecture
8+
9+
### Approach: Shared Database, Shared Schema
10+
11+
- **Single Database**: All tenants share the same database
12+
- **Shared Schema**: All tenants use the same table structure
13+
- **Tenant Isolation**: Data is isolated using `tenant_id` column
14+
- **Row-Level Filtering**: Hibernate filters automatically filter data by tenant
15+
16+
### Benefits
17+
18+
- ✅ Cost-effective (single database)
19+
- ✅ Easy to maintain
20+
- ✅ Simple backup/restore
21+
- ✅ Good performance with proper indexing
22+
- ✅ Easy to scale
23+
24+
## Components
25+
26+
### 1. Tenant Entity
27+
28+
**Location**: `Tenant.java`
29+
30+
Represents a tenant in the system:
31+
- `code`: Unique tenant identifier (e.g., "acme-corp")
32+
- `name`: Tenant display name
33+
- `status`: ACTIVE, SUSPENDED, INACTIVE
34+
- `configuration`: JSON configuration for tenant-specific settings
35+
36+
### 2. Tenant Context
37+
38+
**Location**: `TenantContext.java`
39+
40+
Thread-local storage for current tenant:
41+
- `setTenantId(String)`: Set current tenant ID
42+
- `getTenantId()`: Get current tenant ID
43+
- `setTenantCode(String)`: Set current tenant code
44+
- `getTenantCode()`: Get current tenant code
45+
- `clear()`: Clear tenant context
46+
47+
### 3. Tenant Resolver
48+
49+
**Location**: `TenantResolver.java`
50+
51+
HTTP interceptor that extracts tenant information from:
52+
1. **X-Tenant-Id Header**: Direct tenant ID
53+
2. **X-Tenant-Code Header**: Tenant code (resolved to ID)
54+
3. **Subdomain**: Extracts tenant from subdomain (e.g., `tenant1.example.com`)
55+
4. **JWT Claims**: (Future) Extract from JWT token
56+
57+
### 4. Tenant-Aware Interface
58+
59+
**Location**: `TenantAware.java`
60+
61+
Marker interface for entities that support multi-tenancy:
62+
```java
63+
public interface TenantAware {
64+
String getTenantId();
65+
void setTenantId(String tenantId);
66+
}
67+
```
68+
69+
### 5. Hibernate Tenant Filter
70+
71+
**Location**: `TenantFilter.java`
72+
73+
Automatic row-level filtering using Hibernate filters:
74+
- Filters data by `tenant_id` automatically
75+
- Applied to all queries for tenant-aware entities
76+
77+
## Database Schema
78+
79+
### Tenants Table
80+
81+
```sql
82+
CREATE TABLE tenants (
83+
id BIGINT AUTO_INCREMENT PRIMARY KEY,
84+
code VARCHAR(50) NOT NULL UNIQUE,
85+
name VARCHAR(200) NOT NULL,
86+
email VARCHAR(255),
87+
phone VARCHAR(20),
88+
status VARCHAR(50) NOT NULL DEFAULT 'ACTIVE',
89+
configuration TEXT,
90+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
91+
updated_at TIMESTAMP,
92+
created_by VARCHAR(255),
93+
updated_by VARCHAR(255)
94+
);
95+
```
96+
97+
### Tenant ID Column
98+
99+
All tenant-aware entities have a `tenant_id` column:
100+
- `bookings.tenant_id`
101+
- `passengers.tenant_id`
102+
- (Add to other entities as needed)
103+
104+
## Usage
105+
106+
### 1. Creating a Tenant
107+
108+
```http
109+
POST /api/tenants
110+
Content-Type: application/json
111+
112+
{
113+
"code": "acme-corp",
114+
"name": "ACME Corporation",
115+
"email": "admin@acme.com",
116+
"phone": "+1234567890",
117+
"status": "ACTIVE"
118+
}
119+
```
120+
121+
### 2. Making Requests with Tenant Context
122+
123+
**Option 1: Using X-Tenant-Id Header**
124+
```http
125+
GET /api/bookings
126+
X-Tenant-Id: 1
127+
```
128+
129+
**Option 2: Using X-Tenant-Code Header**
130+
```http
131+
GET /api/bookings
132+
X-Tenant-Code: acme-corp
133+
```
134+
135+
**Option 3: Using Subdomain**
136+
```http
137+
GET http://acme-corp.example.com/api/bookings
138+
```
139+
140+
### 3. Creating a Booking (Tenant-Aware)
141+
142+
```http
143+
POST /api/bookings
144+
X-Tenant-Id: 1
145+
Content-Type: application/json
146+
147+
{
148+
"userId": 1,
149+
"trainId": 1,
150+
"totalFare": 500.00,
151+
"passengers": [...]
152+
}
153+
```
154+
155+
The booking will automatically have `tenant_id` set to `1`.
156+
157+
### 4. Querying Bookings
158+
159+
```http
160+
GET /api/bookings
161+
X-Tenant-Id: 1
162+
```
163+
164+
Only bookings with `tenant_id = 1` will be returned.
165+
166+
## API Endpoints
167+
168+
### Tenant Management
169+
170+
- `POST /api/tenants` - Create tenant
171+
- `GET /api/tenants` - Get all tenants
172+
- `GET /api/tenants/{id}` - Get tenant by ID
173+
- `GET /api/tenants/code/{code}` - Get tenant by code
174+
- `PUT /api/tenants/{id}` - Update tenant
175+
- `DELETE /api/tenants/{id}` - Delete tenant
176+
- `POST /api/tenants/{id}/activate` - Activate tenant
177+
- `POST /api/tenants/{id}/suspend` - Suspend tenant
178+
179+
## Security
180+
181+
### Tenant Isolation
182+
183+
1. **Automatic Filtering**: Hibernate filters automatically filter queries
184+
2. **Service-Level Validation**: Services validate tenant access
185+
3. **Context Validation**: TenantResolver validates tenant exists and is active
186+
187+
### Access Control
188+
189+
- Users can only access data belonging to their tenant
190+
- Cross-tenant access attempts are logged and blocked
191+
- Tenant context is required for all protected endpoints
192+
193+
## Configuration
194+
195+
### Application Properties
196+
197+
```yaml
198+
multi-tenancy:
199+
enabled: true # Enable multi-tenancy support
200+
required: true # Require tenant context for all requests
201+
header-tenant-id: "X-Tenant-Id"
202+
header-tenant-code: "X-Tenant-Code"
203+
subdomain-extraction: true
204+
```
205+
206+
## Implementation Details
207+
208+
### Entity Updates
209+
210+
All tenant-aware entities:
211+
1. Implement `TenantAware` interface
212+
2. Add `tenant_id` column
213+
3. Add Hibernate filter annotations
214+
4. Add index on `tenant_id`
215+
216+
### Service Updates
217+
218+
Services:
219+
1. Set `tenant_id` from context when creating entities
220+
2. Validate tenant access when reading entities
221+
3. Filter queries by tenant
222+
223+
### Repository Updates
224+
225+
Repositories:
226+
1. Use Hibernate filters for automatic filtering
227+
2. Add tenant-specific query methods if needed
228+
229+
## Migration Strategy
230+
231+
### Existing Data
232+
233+
For existing data without `tenant_id`:
234+
1. Create a default tenant
235+
2. Update existing records with default tenant ID
236+
3. Or mark records as "legacy" and handle separately
237+
238+
### Migration Script
239+
240+
```sql
241+
-- Add tenant_id column
242+
ALTER TABLE bookings ADD COLUMN tenant_id VARCHAR(50);
243+
244+
-- Create default tenant
245+
INSERT INTO tenants (code, name, status) VALUES ('default', 'Default Tenant', 'ACTIVE');
246+
247+
-- Update existing records (if needed)
248+
UPDATE bookings SET tenant_id = (SELECT id FROM tenants WHERE code = 'default');
249+
```
250+
251+
## Testing
252+
253+
### Unit Tests
254+
255+
```java
256+
@Test
257+
public void testTenantIsolation() {
258+
// Set tenant context
259+
TenantContext.setTenantId("1");
260+
261+
// Create booking
262+
SimpleBooking booking = new SimpleBooking();
263+
bookingService.createBooking(booking);
264+
265+
// Verify tenant_id is set
266+
assertEquals("1", booking.getTenantId());
267+
268+
// Clear context
269+
TenantContext.clear();
270+
}
271+
```
272+
273+
### Integration Tests
274+
275+
```java
276+
@Test
277+
public void testTenantAccessControl() {
278+
// Create booking for tenant 1
279+
TenantContext.setTenantId("1");
280+
SimpleBooking booking = bookingService.createBooking(new SimpleBooking());
281+
282+
// Try to access as tenant 2
283+
TenantContext.setTenantId("2");
284+
Optional<SimpleBooking> result = bookingService.getBookingById(booking.getId());
285+
286+
// Should return empty
287+
assertFalse(result.isPresent());
288+
}
289+
```
290+
291+
## Best Practices
292+
293+
### 1. Always Set Tenant Context
294+
295+
- Set tenant context early in request processing
296+
- Validate tenant exists and is active
297+
- Clear context after request completion
298+
299+
### 2. Validate Tenant Access
300+
301+
- Always validate tenant access in services
302+
- Log access violations
303+
- Return appropriate error messages
304+
305+
### 3. Index Tenant ID
306+
307+
- Add indexes on `tenant_id` columns
308+
- Improves query performance
309+
- Essential for large datasets
310+
311+
### 4. Tenant Configuration
312+
313+
- Use `configuration` JSON field for tenant-specific settings
314+
- Support feature flags per tenant
315+
- Customize behavior per tenant
316+
317+
### 5. Monitoring
318+
319+
- Monitor tenant usage
320+
- Track cross-tenant access attempts
321+
- Alert on suspicious activity
322+
323+
## Troubleshooting
324+
325+
### Issue: Tenant Context Not Set
326+
327+
**Symptoms**: Requests fail with "Tenant context is required"
328+
329+
**Solution**:
330+
- Ensure `X-Tenant-Id` or `X-Tenant-Code` header is sent
331+
- Check TenantResolver is registered
332+
- Verify interceptor is not excluded
333+
334+
### Issue: Cross-Tenant Data Access
335+
336+
**Symptoms**: User can see data from other tenants
337+
338+
**Solution**:
339+
- Verify Hibernate filter is enabled
340+
- Check service-level validation
341+
- Review tenant context propagation
342+
343+
### Issue: Performance Issues
344+
345+
**Symptoms**: Slow queries with tenant filtering
346+
347+
**Solution**:
348+
- Ensure indexes on `tenant_id`
349+
- Review query plans
350+
- Consider tenant-specific caching
351+
352+
## Future Enhancements
353+
354+
1. **JWT Integration**: Extract tenant from JWT claims
355+
2. **Tenant-Specific Databases**: Support separate databases per tenant
356+
3. **Tenant Analytics**: Per-tenant usage analytics
357+
4. **Tenant Billing**: Track usage per tenant
358+
5. **Tenant Onboarding**: Automated tenant provisioning
359+
360+
## Files Created
361+
362+
### Core Components
363+
- `Tenant.java` - Tenant entity
364+
- `TenantRepository.java` - Tenant repository
365+
- `TenantContext.java` - Thread-local tenant context
366+
- `TenantResolver.java` - HTTP interceptor
367+
- `TenantAware.java` - Marker interface
368+
- `TenantService.java` - Tenant business logic
369+
- `TenantController.java` - Tenant REST API
370+
- `TenantConfig.java` - Configuration
371+
- `TenantFilter.java` - Hibernate filter
372+
373+
### Database Migrations
374+
- `V7__Create_tenants_table.sql` - Create tenants table
375+
- `V8__Add_tenant_id_to_bookings.sql` - Add tenant_id columns
376+
377+
### Documentation
378+
- `MULTI_TENANCY_IMPLEMENTATION.md` - This file
379+
380+
## Conclusion
381+
382+
Multi-tenancy support is now implemented in the booking service. The same pattern can be applied to other services (train, user, payment, notification) by:
383+
384+
1. Adding tenant-aware interfaces to entities
385+
2. Adding `tenant_id` columns
386+
3. Updating services to use tenant context
387+
4. Adding tenant validation
388+
389+
This provides a solid foundation for SaaS capabilities with proper tenant isolation and security.
390+

0 commit comments

Comments
 (0)