• Section 1-4 : Tenant Customization Microservices
Prerequisites Check
- Please complete below, if not done earlier:
Kafka should be up and running, else backend projects will compile, but will NOT run!
Section 1-4 : Build Tenant Specific Customization Using Microservices
For Customizations, build and Tenant-specific Microservices using one of the communication methodology.
Building squence
Microservices Building squence one should follow,
1] tenant-2-validation-service
2] tenant-manager-cust-service
3] order-service-v2
4] tenant-3-shipping-service
But Listed here as per Requirements Specs.
Build Backends For Main Product And Customizations
• For order-service-v2
Note: This backend is extending of emapi-order-svc-v1, so this will be created from its copy.
- COPY folder
C:\work\saas-multi\main-prod\emapi-order-svc-v1
asemapi-order-svc-v2
- dest full path
C:\work\saas-multi\main-prod\emapi-order-svc-v2
- dest full path
Build/Extend order-service-v2
- Project is already Compiled and Verified
- Extending order-service-v1 to order-service-v2 is Take Home Assignment.
- You can add call to tenant-manager-cust-service after the call for inveninventory-quantity-check-service.
- With data e.g. : tenantId=3&customerId=2&orderId=3&productId=2&orderQuantity=25
- And get back new orderStatus to use in saving order.
Build tenant-manager-cust-service
Build And Run Project emapi-tenant-mgr-cust-svc
- Compile and Run project
mvn -version
java -version
cd C:\work\saas-multi\tenant-cust\emapi-tenant-mgr-cust-svc
mvn package
java -jar app\dbrest\target\dbrest-1.0-SNAPSHOT.jar
- Verify Run Ok
Verify Service APIs
Open Swagger API explorer and test API endpoints
Shutdown Running Project - Ctrl-C
Low-Code Customize to Build Microservice
Open Project in Explorer
Import Maven Project in IntelliJ IDEA
- C:\work\saas-multi\tenant-cust\emapi-tenant-mgr-cust-svc
Add New API endpoint
Add New API endpoint details
- Add API endpoint to
C:\work\saas-multi\tenant-cust\emapi-tenant-mgr-cust-svc\app\dbrest\src\main\java\com\example\emapi\app\Tenant\TenantDataRestController.java
- Uncomment all imports for WebClient use, under heading:
// Uncomment For Communicate Consumer-Service and Data Mesh API
Add imports for Kafka Order Event
import com.example.emapi.app.EmKafkaEvent;
import com.example.emapi.app.EmKafkaProducerService;
import com.example.emapi.app.Orders.Orders;
Add logger variable, if not present, and KafkaProducer:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class TenantDataRestController {
private final Logger logger = LogManager.getLogger(this.getClass());
// Kafka Events
@Autowired
EmKafkaProducerService EmKafkaProducerService;
Add new function:
// -------------------- TenantMgrHandleCust -------------------------
@GetMapping("tenant/TenantMgrHandleCust")
public ResponseEntity<String> TenantMgrHandleCust(@RequestParam long tenantId,
@RequestParam long customerId,
@RequestParam long orderId,
@RequestParam long productId,
@RequestParam long orderQuantity )
throws Exception
{
//Process tenant customizations and update orderStatus and return
logger.info("emapi-tenant-mgr-cust-svc: Process Begin : tenantId: "+tenantId+" , customerId: "+customerId+", orderId: "+orderId+", productId: "+productId+", orderQuantity: "+orderQuantity);
String orderStatus = "Created";
List<Tenant> TenantList = tenantService.TenantQuery(tenantId);
if (TenantList.isEmpty()) {
return new ResponseEntity<>(orderStatus, HttpStatus.OK);
}
Tenant tenantRec = TenantList.get(0);
long customizationRequired = tenantRec.getCustomizationRequired();
String custMicrosvcType = tenantRec.getCustMicrosvcType();
String customizationService = tenantRec.getCustomizationService();
if (customizationRequired == 1) {
logger.info("emapi-tenant-mgr-cust-svc: Customization Required : tenantId: " + tenantId);
logger.info("emapi-tenant-mgr-cust-svc: Customization Details : tenantId: " + tenantId + ", Type: ["+custMicrosvcType+"], Service: "+customizationService );
if (custMicrosvcType.equals("synchronous-webclient")) {
if (customizationService.equals("tenant-2-validation-service")) {
logger.info("emapi-tenant-mgr-cust-svc: Call sync tenant-2-validation-service ");
// ---- Consumer Service : Call Producer-Service--------------------------------------------
// Get data from REST API call (sample shows getting data from same table ViewAll API call)
// Customize to call another microservice on different server-port / URI
// - Provisioning API contract data model java class for another microservice API
//-------------------------
String get_data_rest_url = "http://127.0.0.1:9076/emdbrest/tenant/ValidateOrder?tenantId="+tenantId+"&orderQuantity="+orderQuantity;
WebClient webClientRest = WebClient.builder().baseUrl(get_data_rest_url)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).build();
Mono<Boolean> responseRest =
webClientRest.get()
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).retrieve()
.bodyToMono(new ParameterizedTypeReference<Boolean>() {});
Boolean validOrder = responseRest.block();
System.out.println("Fetched From Validation-Service - Status : "+validOrder);
//-------------------------
if (validOrder == false) {
orderStatus = "Created-T2-OrderValidationFailed";
} else {
orderStatus = "Created-T2-OrderValidationPassed";
}
}
} else if (custMicrosvcType.equals("asynchronous-event")) {
if (customizationService.equals("tenant-3-shipping-service")) {
logger.info("emapi-tenant-mgr-cust-svc: Send order event for tenant-3-shipping-service ");
//send order event
Orders myOrder = new Orders();
myOrder.setOrderId(orderId);
myOrder.setCustomerId(customerId);
myOrder.setTenantId(tenantId);
myOrder.setProductId(productId);
myOrder.setOrderQuantity(orderQuantity);
EmKafkaEvent EmKafkaEvent = new EmKafkaEvent(
"Producer Orders Event - Created Orders",
"CREATED",
myOrder
);
EmKafkaProducerService.producerSendMessage(EmKafkaEvent);
orderStatus = "Created-T3-OrderEventForShippingSent";
}
}
} else {
logger.info("emapi-tenant-mgr-cust-svc: Customization NOT Required : tenantId: " + tenantId);
}
logger.info("emapi-tenant-mgr-cust-svc: Processed Status : orderStatus: " + orderStatus);
return new ResponseEntity<>(orderStatus, HttpStatus.OK);
}
- IMP: In order to run Kafka Sreams App from multiple microservices, apply below change:
- Edit
C:\work\saas-multi\tenant-cust\emapi-tenant-mgr-cust-svc\lib\base-app\src\main\java\com\example\emapi\app\EmKafkaStreamsConfig.java
- Make application id unique: Change from
"em_kafka_streams_app"
to"em_kafka_streams_app_9076"
- Edit
NOTE: There is default Kafka Event Consumer in
emapi
project, we will disable/remove it after testing this service. Lateron shipping service will implement Kafka Event Consumer.Before Running project
- Make sure Service tenant-2-validation-service is Running:
2024-09-19T13:56:00.360+05:30 INFO 35260 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 9076 (http) with context path ''
2024-09-19T13:56:00.375+05:30 INFO 35260 --- [ main] c.e.emapi.app.EmDbRestAppRestSpringApp : Started EmDbRestAppRestSpringApp in 9.303 seconds (process running for 9.878)
- Kafka Servers are Running:
Re-Compile and Run project
Verify new Service API ok
Open Swagger API explorer and test API endpoints
Swagger API call
New API endpoint - Verify running ok details
Service API call: Validate Call:
curl -X 'GET' \
'http://127.0.0.1:9074/emdbrest/tenant/TenantMgrHandleCust?tenantId=3&customerId=2&orderId=3&productId=2&orderQuantity=25' \
-H 'accept: application/hal+json'
Returns:
Created-T2-OrderValidationPassed
Test Validate Fail: http://127.0.0.1:9074/emdbrest/tenant/TenantMgrHandleCust?tenantId=3&customerId=2&orderId=3&productId=2&orderQuantity=5'
Created-T2-OrderValidationFailed
Log write check:
2024-09-19T14:39:55.956+05:30 INFO 33868 --- [nio-9074-exec-3] c.e.e.a.Tenant.TenantDataRestController : emapi-tenant-mgr-cust-svc: Process Begin : tenantId: 3 , customerId: 2, orderId: 3, productId: 2, orderQuantity: 5
2024-09-19T14:39:55.957+05:30 INFO 33868 --- [nio-9074-exec-3] c.e.e.a.Tenant.TenantDataRestController : emapi-tenant-mgr-cust-svc: Customization Required : tenantId: 3
2024-09-19T14:39:55.957+05:30 INFO 33868 --- [nio-9074-exec-3] c.e.e.a.Tenant.TenantDataRestController : emapi-tenant-mgr-cust-svc: Customization Details : tenantId: 3, Type: [synchronous-webclient], Service: tenant-2-validation-service
2024-09-19T14:39:55.957+05:30 INFO 33868 --- [nio-9074-exec-3] c.e.e.a.Tenant.TenantDataRestController : emapi-tenant-mgr-cust-svc: Call sync tenant-2-validation-service
2024-09-19T14:39:55.958+05:30 DEBUG 33868 --- [nio-9074-exec-3] o.s.w.r.f.client.ExchangeFunctions : [12f52aea] HTTP GET http://127.0.0.1:9076/emdbrest/tenant/ValidateOrder?tenantId=3&orderQuantity=5
2024-09-19T14:39:55.971+05:30 DEBUG 33868 --- [ctor-http-nio-3] o.s.w.r.f.client.ExchangeFunctions : [12f52aea] [23c064f0-1] Response 200 OK
2024-09-19T14:39:55.972+05:30 DEBUG 33868 --- [ctor-http-nio-3] org.springframework.web.HttpLogging : [12f52aea] [23c064f0-1] Decoded [false]
Fetched From Validation-Service - Status : false
2024-09-19T14:39:55.972+05:30 INFO 33868 --- [nio-9074-exec-3] c.e.e.a.Tenant.TenantDataRestController : emapi-tenant-mgr-cust-svc: Processed Status : orderStatus: Created-T2-OrderValidationFailed
Service API call: Send Order Event Call:
curl -X 'GET' \
'http://127.0.0.1:9074/emdbrest/tenant/TenantMgrHandleCust?tenantId=4&customerId=3&orderId=5&productId=3&orderQuantity=20' \
-H 'accept: application/hal+json'
Returns:
Created-T3-OrderEventForShippingSent
Log write check:
2024-09-19T14:53:13.583+05:30 INFO 33868 --- [nio-9074-exec-3] c.e.e.a.Tenant.TenantDataRestController : emapi-tenant-mgr-cust-svc: Process Begin : tenantId: 4 , customerId: 3, orderId: 5, productId: 3, orderQuantity: 20
2024-09-19T14:53:14.038+05:30 INFO 33868 --- [nio-9074-exec-3] c.e.e.a.Tenant.TenantDataRestController : emapi-tenant-mgr-cust-svc: Customization Required : tenantId: 4
2024-09-19T14:53:14.038+05:30 INFO 33868 --- [nio-9074-exec-3] c.e.e.a.Tenant.TenantDataRestController : emapi-tenant-mgr-cust-svc: Customization Details : tenantId: 4, Type: [asynchronous-event], Service: tenant-3-shipping-service
2024-09-19T14:53:14.038+05:30 INFO 33868 --- [nio-9074-exec-3] c.e.e.a.Tenant.TenantDataRestController : emapi-tenant-mgr-cust-svc: Send order event for tenant-3-shipping-service
2024-09-19T14:53:14.039+05:30 INFO 33868 --- [nio-9074-exec-3] c.e.emapi.app.EmKafkaProducerService : Producer: EmKafkaEvent Event => Producer Orders Event - Created Orders Orders [ orderId = 5 ]
2024-09-19T14:53:14.062+05:30 INFO 33868 --- [nio-9074-exec-3] c.e.e.a.Tenant.TenantDataRestController : emapi-tenant-mgr-cust-svc: Processed Status : orderStatus: Created-T3-OrderEventForShippingSent
2024-09-19T14:53:14.063+05:30 DEBUG 33868 --- [nio-9074-exec-3] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/hal+json', given [application/hal+json] and supported [text/plain, */*, application/json, application/*+json]
2024-09-19T14:53:14.063+05:30 DEBUG 33868 --- [nio-9074-exec-3] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing ["Created-T3-OrderEventForShippingSent"]
2024-09-19T14:53:14.064+05:30 DEBUG 33868 --- [nio-9074-exec-3] o.s.web.servlet.DispatcherServlet : Completed 200 OK
2024-09-19T14:53:14.064+05:30 DEBUG 33868 --- [nio-9074-exec-3] o.s.w.f.CommonsRequestLoggingFilter : API LOGGING: GET /emdbrest/tenant/TenantMgrHandleCust?tenantId=4&customerId=3&orderId=5&productId=3&orderQuantity=20]
2024-09-19T14:53:14.109+05:30 INFO 33868 --- [ntainer#0-0-C-1] c.e.emapi.app.EmKafkaConsumerService : EmKafkaEvent Event consumed in --- Service => Producer Orders Event - Created Orders {orderId=5, customerId=3, tenantId=4, productId=3, orderDate=null, orderQuantity=20, orderStatus=null, created=null, updated=null}
2024-09-19T14:53:36.553+05:30 INFO 33868 --- [-StreamThread-1] o.a.k.s.p.internals.StreamThread : stream-thread [em_kafka_streams_app-b543d357-f7b7-4dce-a84c-728842c07915-StreamThread-1] Processed 29 total records, ran 0 punctuators, and committed 0 total tasks since the last update
- NOTE: The log shows default Kafka Event Consumer has consumed the event, last 2 lines of log.
NOTE: Disable/remove default Kafka Event Consumer in
emapi
project. Lateron shipping service will implement Kafka Event Consumer.- Shutdown Service, Remove Java File below
C:\work\saas-multi\tenant-cust\emapi-tenant-mgr-cust-svc\lib\base-app\src\main\java\com\example\emapi\app\EmKafkaConsumerService.java
- Then re-compile and Re-run project
Create 2-3 more Order Events which we can test / consume in shipping service.
Tutorial : Declarative APIs
Declarative APIs Development
In previous microservice tenant-manager-cust-service
implementation,
- The API endpoints implemented for SaaS Multitenant Customization are declarative. The API endpoint call to tenant manager customization tells to do the tenant-based customization, without giving details on what could be steps for such customization for each tenant.
Service API call:
curl -X 'GET' \
'http://127.0.0.1:9074/emdbrest/tenant/TenantMgrHandleCust?tenantId=4&customerId=3&orderId=5&productId=3&orderQuantity=20' \
-H 'accept: application/hal+json'
Handles all details such as Order Event Call for Tenant 3 and Returns:
Created-T3-OrderEventForShippingSent
Build tenant-2-validation-service
Build And Run Project emapi-tenant-2-svc
- Compile and Run project
mvn -version
java -version
cd C:\work\saas-multi\tenant-cust\emapi-tenant-2-svc
mvn package
java -jar app\dbrest\target\dbrest-1.0-SNAPSHOT.jar
- Verify Run Ok
Verify Service APIs
Open Swagger API explorer and test API endpoints
Shutdown Running Project - Ctrl-C
Low-Code Customize to Build Microservice
Open Project in Explorer
Import Maven Project in IntelliJ IDEA
- C:\work\saas-multi\tenant-cust\emapi-tenant-2-svc
Add New API endpoint
Add New API endpoint details
- Add API endpoint to
C:\work\saas-multi\tenant-cust\emapi-tenant-2-svc\app\dbrest\src\main\java\com\example\emapi\app\Tenant\TenantDataRestController.java
Add logger variable, if not present:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class TenantDataRestController {
private final Logger logger = LogManager.getLogger(this.getClass());
Add new function:
// -------------------- ValidateOrder -------------------------
@GetMapping("tenant/ValidateOrder")
public ResponseEntity<Boolean> TenantValidateOrder(@RequestParam long tenantId, @RequestParam long orderQuantity )
throws Exception
{
Boolean validOrder = false;
List<Tenant> TenantList = tenantService.TenantQuery(tenantId);
if (TenantList.isEmpty()) {
return new ResponseEntity<>(validOrder, HttpStatus.OK);
}
Tenant tenantRec = TenantList.get(0);
long minOrderQuantity = tenantRec.getMinOrderQuantity();
logger.info("tenant-2-validation-service: Check: tenantId: "+tenantId+", minOrderQuantity: "+minOrderQuantity);
if ( orderQuantity >= minOrderQuantity ) {
validOrder = true;
//LOG message
logger.info("tenant-2-validation-service: PASS For : tenantId: "+tenantId+", orderQuantity: "+orderQuantity);
} else {
logger.info("tenant-2-validation-service: FAIL For : tenantId: "+tenantId+", orderQuantity: "+orderQuantity);
}
return new ResponseEntity<>(validOrder, HttpStatus.OK);
}
Re-Compile and Run project
Verify new Service API ok
Open Swagger API explorer and test API endpoints
Swagger API call
New API endpoint - Verify running ok details
Service API call: Validate Call:
curl -X 'GET' \
'http://127.0.0.1:9076/emdbrest/tenant/ValidateOrder?tenantId=3&orderQuantity=60' \
-H 'accept: application/hal+json'
Returns:
true
Test Validate Fail: http://127.0.0.1:9076/emdbrest/tenant/ValidateOrder?tenantId=3&orderQuantity=5
false
Log write check:
2024-09-19T11:04:33.212+05:30 INFO 56092 --- [nio-9076-exec-6] c.e.e.a.Tenant.TenantDataRestController : tenant-2-validation-service: Check: tenantId: 3, minOrderQuantity: 10
2024-09-19T11:04:33.212+05:30 INFO 56092 --- [nio-9076-exec-6] c.e.e.a.Tenant.TenantDataRestController : tenant-2-validation-service: FAIL For : tenantId: 3, orderQuantity: 5
Build tenant-3-shipping-service
Build And Run Project emapi-tenant-3-svc
- Compile and Run project
mvn -version
java -version
cd C:\work\saas-multi\tenant-cust\emapi-tenant-3-svc
mvn package
java -jar app\dbrest\target\dbrest-1.0-SNAPSHOT.jar
- Verify Run Ok
Verify Service APIs
Open Swagger API explorer and test API endpoints
Shutdown Running Project - Ctrl-C
Low-Code Customize to Build Microservice
Open Project in Explorer
Import Maven Project in IntelliJ IDEA
- C:\work\saas-multi\tenant-cust\emapi-tenant-3-svc
We can use Default Kafka Event Consumer in
emapi
project.
Add New API endpoint details
Copy Orders.java model file from order-service-v1 to dest location:
C:\work\saas-multi\tenant-cust\emapi-tenant-3-svc\lib\base-app\src\main\java\com\example\emapi\app\Orders\Orders.java
Edit Java File below to consume and process Order Event
C:\work\saas-multi\tenant-cust\emapi-tenant-3-svc\lib\base-app\src\main\java\com\example\emapi\app\EmKafkaConsumerService.java
Add imports and ShippingService
import com.fasterxml.jackson.databind.ObjectMapper;
import com.example.emapi.app.Orders.Orders;
import com.example.emapi.app.Shipping.Shipping;
import com.example.emapi.app.Shipping.ShippingService;
import java.util.Calendar;
@Configuration
@EnableKafka
public class EmKafkaConsumerService {
@Autowired
ShippingService shippingService;
Edit function as below:
public void consume(EmKafkaEvent event) throws Exception {
LOGGER.info(String.format("EmKafkaEvent Event consumed in --- Service => %s", event.getMessage() + " " + event.getEmEntity()));
// Consume event specific to a record or data model e.g. Orders
// add to import section: import com.example.emapi.app.Orders.Orders
LOGGER.info(String.format("EmKafkaEvent T = %s",event.getEmEntity().getClass()));
Orders _Orders = new Orders();
ObjectMapper mapper = new ObjectMapper();
_Orders = mapper.convertValue(event.getEmEntity(), Orders.class);
long orderId = _Orders.getOrderId();
long orderQuantity = _Orders.getOrderQuantity();
long customerId = _Orders.getCustomerId();
long tenantId = _Orders.getTenantId();
long productId = _Orders.getProductId();
// Create shipping record for the given order
// using shipping table gen code, e.g.
// shippingService.ShippingCreate()
Calendar currTime = Calendar.getInstance();
currTime.setTime(new java.util.Date());
Shipping shippingRec = new Shipping();
shippingRec.setShippingId(orderId);
shippingRec.setOrderId(orderId);
shippingRec.setCustomerId(customerId);
shippingRec.setTenantId(tenantId);
shippingRec.setProductId(productId);
shippingRec.setShippingQuantity(orderQuantity);
shippingRec.setShippingDate(currTime);
shippingRec.setCreated(currTime);
Shipping _Shipping =
shippingService.ShippingCreate(shippingRec);
LOGGER.info(String.format("EmKafkaEvent Event consume : Shipping created for orderId => %s", orderId));
LOGGER.info(String.format("EmKafkaEvent Event consume : Shipping Saved => %s", _Shipping.toString()));
//}
LOGGER.info(String.format("EmKafkaEvent Event consume done --- Service => %s", event.getMessage() + " " + event.getEmEntity()));
// handle actions post consuming here ...
}
- IMP: In order to run Kafka Sreams App from multiple microservices, apply below change:
- Edit
C:\work\saas-multi\tenant-cust\emapi-tenant-3-svc\lib\base-app\src\main\java\com\example\emapi\app\EmKafkaStreamsConfig.java
- Make application id unique: Change from
"em_kafka_streams_app"
to"em_kafka_streams_app_9078"
- Edit
Re-Compile and Run project
Verify new Service API ok
Open Swagger API explorer and test API endpoints
The Event Consumed are async operations and can be checked in logs.
New API endpoint - Verify running ok details
Check Log for consumption of previous events on Kafka queue:
2024-09-19T18:08:50.910+05:30 INFO 11172 --- [ntainer#0-0-C-1] c.e.emapi.app.EmKafkaConsumerService : EmKafkaEvent Event consumed in --- Service => Producer Orders Event - Created Orders {orderId=7, customerId=3, tenantId=4, productId=2, orderDate=null, orderQuantity=18, orderStatus=null, created=null, updated=null}
...
2024-09-19T18:08:52.559+05:30 INFO 11172 --- [ntainer#0-0-C-1] c.e.emapi.app.EmKafkaConsumerService : EmKafkaEvent Event consume : Shipping created for orderId => 7
2024-09-19T18:08:52.559+05:30 INFO 11172 --- [ntainer#0-0-C-1] c.e.emapi.app.EmKafkaConsumerService : EmKafkaEvent Event consume : Shipping Saved => Shipping [ shippingId = 7 ]
2024-09-19T18:08:52.559+05:30 INFO 11172 --- [ntainer#0-0-C-1] c.e.emapi.app.EmKafkaConsumerService : EmKafkaEvent Event consume done --- Service => Producer Orders Event - Created Orders {orderId=7, customerId=3, tenantId=4, productId=2, orderDate=null, orderQuantity=18, orderStatus=null, created=null, updated=null}
- Verify Shipping created
[
{
"shippingId": 7,
"orderId": 7,
"customerId": 3,
"tenantId": 4,
"productId": 2,
"shippingDate": "2024-09-19",
"shippingQuantity": 18,
"shippingStatus": null,
"created": "2024-09-19 18:08",
"updated": null
}
]
Summary
Building below is complete!
- Enterprise Microservices Development - Main Product Microservices V2
- Tenant Customization Microservices