Skip to main content

• Section 1-4 : Tenant Customization Microservices

Prerequisites Check

caution

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 as emapi-order-svc-v2
    • dest full path C:\work\saas-multi\main-prod\emapi-order-svc-v2

Build/Extend order-service-v2

  • Project is already Compiled and Verified
info
  • 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

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

Project View

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"
  • 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

caution
  • 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:

Kafka Server

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

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

Project View

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);

}

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

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.

Project View

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"
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}

[
{
"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

info

Building below is complete!

  • Enterprise Microservices Development - Main Product Microservices V2
    • Tenant Customization Microservices