Supercharging Productivity in Microservice Development With AI Tools
Utilizing AI tools in microservice development streamlines the coding process, enhancing efficiency and accuracy, especially with extensive data models.
Join the DZone community and get the full member experience.
Join For FreeBuilding APIs and their related components is often a tedious task, especially when dealing with complex data models and architectures like microservices. Repetitive coding, setting up the configurations, and the overhead of building unit tests can quickly become time-consuming. Here's how AI tools, like ChatGPT, can be a game-changer.
Harnessing AI Tools (Chat GPT) In API Development
To understand the capabilities of ChatGPT, let's dive into a hands-on example. We're tasked with developing a REST API to manage users' Personally Identifiable Information (PII). The process entails:
- Database object creation (Stored procedure)
- Repository (Interface for Database Interaction and Data Access Object)
- Service (Business Logic Layer)
- REST Controller (Exposing Endpoints)
Database Objects
To build a stored procedure, first provide the table structure to ChatGPT. After that, supply a sample stored procedure. This will allow ChatGPT to anchor to the stored procedure pattern and construct a new procedure for the specified table. Below is the table we provided:
CREATE TABLE `user_pii_data_repo` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` int DEFAULT NULL,
`loan_id` int DEFAULT NULL,
`ext_partner_name` varchar(30) NOT NULL,
`ext_partner_source` varchar(30) NOT NULL,
`phase` varchar(20) NOT NULL,
`session_uuid` varchar(36) DEFAULT NULL,
`first_name` varchar(300) DEFAULT NULL,
`last_name` varchar(300) DEFAULT NULL,
`address1` varchar(300) DEFAULT NULL,
`address2` varchar(300) DEFAULT NULL,
`city` varchar(300) DEFAULT NULL,
`state` char(2) DEFAULT NULL,
`zip` varchar(300) DEFAULT NULL,
`ssn` varchar(300) DEFAULT NULL,
`dob` date DEFAULT NULL,
`phone` varchar(300) DEFAULT NULL,
`payload_path` varchar(500) DEFAULT NULL,
PRIMARY KEY (`id`)
);
Here is the stored procedure that has been generated:
CREATE PROCEDURE `p_get_user_pii_data`(IN v_user_id INT, IN v_loan_id INT)
BEGIN
SELECT
id, user_id, loan_id, ext_partner_name,
ext_partner_source, phase, session_uuid, first_name,
last_name, address1, address2, city,
state, zip, ssn, dob,
phone, payload_path
FROM
user_pii_data_repo
WHERE
user_id = v_user_id
AND
loan_id = v_loan_id;
END;
Here is the conversation link.
Repository (Interface for Database Interaction and Data Access Object)
In our endeavor to construct the repository layer, we employed a dedicated ChatGPT thread. We supplied it with the previously mentioned stored procedure and table definition. Leveraging ChatGPT's capabilities, we successfully generated:
- A JPA entity.
- A comprehensive Repository class encompassing both the interface and its implementation.
Post-generation, we tasked ChatGPT with the creation of unit test cases. Impressively, the outcomes were near-perfect. A few minor adjustments were necessary, predominantly related to namespaces and some other minor details.
Below are the generated classes and code:
Repository Interface and Implementation
package com.demo.app.api.core.repository.user_pii;
import com.demo.app.api.core.model.user_pii.UserPiiData;
import java.util.List;
public interface UserPiiDataRepository {
List<UserPiiData> getUserPiiData(Integer userId, Integer loanId);
}
package com.demo.app.api.core.repository.user_pii;
import com.demo.app.api.core.model.user_pii.UserPiiData;
import com.demo.app.api.core.repository.EntityManagerThreadLocalHelper;
import com.demo.app.api.core.services.common.CryptoService;
import com.demo.app.api.utils.Utility;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Repository;
import javax.persistence.*;
import java.util.List;
@Repository
@Qualifier("userPiiDataRepository")
public class UserPiiDataRepositoryImpl implements UserPiiDataRepository {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
EntityManagerThreadLocalHelper threadLocalHelper;
@Autowired
CryptoService cryptoService;
@Override
public List<UserPiiData> getUserPiiData(Integer userId, Integer loanId) {
List<UserPiiData> results = null;
try {
EntityManager entityManager = threadLocalHelper.getEntityManager();
StoredProcedureQuery query = entityManager.createStoredProcedureQuery(UserPiiDataDbConstants.PROC_UW_AP_GET_USER_PII_DATA, UserPiiData.class);
query.registerStoredProcedureParameter(UserPiiDataDbConstants.V_PARAM_USER_ID, Integer.class, ParameterMode.IN);
query.registerStoredProcedureParameter(UserPiiDataDbConstants.V_PARAM_LOAN_ID, Integer.class, ParameterMode.IN);
query.setParameter(UserPiiDataDbConstants.V_PARAM_USER_ID, userId);
query.setParameter(UserPiiDataDbConstants.V_PARAM_LOAN_ID, loanId);
query.execute();
results = query.getResultList();
for (UserPiiData userPiiData :
results) {
if (userPiiData != null) {
if (userPiiData.getFirstName() != null) {
String decryptedFirstName = this.cryptoService.decrypt(userPiiData.getFirstName());
userPiiData.setFirstName(decryptedFirstName);
}
.....
if (userPiiData.getDob() != null) {
String formattedDate = Utility.getFormattedDobMMDDYYYY(userPiiData.getDob());
userPiiData.setFormattedDob(formattedDate);
}
}
entityManager.detach(userPiiData);
}
threadLocalHelper.closeEntityManager();
} catch(Exception ex) {
logger.error("UserPiiDataRepositoryImpl::getUserPiiData - an error occurred while fetching user pii data", ex);
threadLocalHelper.closeEntityManager();
}
return results;
}
}
package com.demo.app.api.core.repository.user_pii;
public interface UserPiiDataDbConstants {
String PROC_UW_AP_GET_USER_PII_DATA = "p_uw_ap_get_user_pii_data";
String V_PARAM_USER_ID = "v_user_id";
String V_PARAM_LOAN_ID = "v_loan_id";
}
Unit Tests
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import com.demo.app.api.core.model.user_pii.UserPiiData;
import com.demo.app.api.core.repository.EntityManagerReadOnlyThreadLocalHelper;
import com.demo.app.api.core.services.common.CryptoService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import javax.persistence.EntityManager;
import javax.persistence.StoredProcedureQuery;
import java.util.Collections;
import java.util.Date;
import java.util.List;
public class UserPiiDataRepositoryImplTest {
@InjectMocks
private UserPiiDataRepositoryImpl userPiiDataRepository;
@Mock
private EntityManagerReadOnlyThreadLocalHelper threadLocalHelper;
@Mock
private CryptoService cryptoService;
@Mock
private EntityManager entityManager;
@Mock
private StoredProcedureQuery storedProcedureQuery;
@BeforeEach
public void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
public void testGetUserPiiData() {
// Arrange
UserPiiData sampleData = new UserPiiData();
sampleData.setFirstName("encryptedFirstName");
sampleData.setLastName("encryptedLastName");
sampleData.setAddress1("encryptedAddress1");
sampleData.setCity("encryptedCity");
sampleData.setZip("encryptedZip");
sampleData.setSsn("encryptedSsn");
sampleData.setPhone("encryptedPhone");
sampleData.setDob(new Date());
when(threadLocalHelper.getEntityManager()).thenReturn(entityManager);
when(entityManager.createStoredProcedureQuery(UserPiiDataDbConstants.PROC_UW_AP_GET_USER_PII_DATA, UserPiiData.class)).thenReturn(storedProcedureQuery);
when(storedProcedureQuery.getResultList()).thenReturn(Collections.singletonList(sampleData));
when(cryptoService.decrypt("encryptedFirstName")).thenReturn("John");
when(cryptoService.decrypt("encryptedLastName")).thenReturn("Doe");
when(cryptoService.decrypt("encryptedAddress1")).thenReturn("123 Main St.");
when(cryptoService.decrypt("encryptedCity")).thenReturn("Test City");
when(cryptoService.decrypt("encryptedZip")).thenReturn("12345");
when(cryptoService.decrypt("encryptedSsn")).thenReturn("123-45-6789");
when(cryptoService.decrypt("encryptedPhone")).thenReturn("123-456-7890");
// Act
List<UserPiiData> result = userPiiDataRepository.getUserPiiData(1, 100);
// Assert
assertEquals(1, result.size());
UserPiiData fetchedData = result.get(0);
assertEquals("John", fetchedData.getFirstName());
assertEquals("Doe", fetchedData.getLastName());
assertEquals("123 Main St.", fetchedData.getAddress1());
assertEquals("Test City", fetchedData.getCity());
assertEquals("12345", fetchedData.getZip());
assertEquals("123-45-6789", fetchedData.getSsn());
assertEquals("123-456-7890", fetchedData.getPhone());
}
}
During our initial execution, we encountered challenges with the code generated for the repository implementation. The data stored in our database was encrypted, necessitating decryption of the persisted values. We refined the repository layer's code to address this, integrating decryption mechanisms.
After implementing the modifications, we supplied ChatGPT with the updated repository code, seeking a corresponding unit test case. Remarkably, ChatGPT furnished us with a perfectly functional unit test case.
For those interested in the detailed dialogue, here's the link to our ChatGPT conversation thread.
Building Business Service layer
To deepen our exploration, we extended our requirements to an existing ChatGPT service sample, specifying dependencies like the JPA entity and the repository interface.
The AI tool promptly generated classes. The Service Interface outlined a clear contract for fetching user PII data based on user and loan IDs. Its implementation, UserPIIDataServiceImpl
, utilized the JPA repository to fetch the required data, and if null, would return an empty list — a thoughtful touch for avoiding potential null pointer exceptions.
The unit test, UserPIIDataServiceImplTest
, was comprehensive. Using Mockito for mock objects, the test verified the service's functionality by arranging mock data, calling the service, and asserting the expected results.
Below are the generated classes and code:
Service Interface and Implementation
package com.demo.app.api.core.services.user_pii;
import com.demo.app.api.core.model.user_pii.UserPiiData;
import java.util.List;
public interface UserPIIDataService {
List<UserPiiData> getUserPiiData(Integer userId, Integer loanId);
}
package com.demo.app.api.core.services.user_pii;
import com.demo.app.api.core.model.user_pii.UserPiiData;
import com.demo.app.api.core.repository.user_pii.UserPiiDataRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
@Service
public class UserPIIDataServiceImpl implements UserPIIDataService {
@Autowired
UserPiiDataRepository userPiiDataRepository;
@Override
public List<UserPiiData> getUserPiiData(Integer userId, Integer loanId) {
List<UserPiiData> piiData = userPiiDataRepository.getUserPiiData(userId, loanId);
return piiData != null ? piiData : Collections.emptyList();
}
}
Unit Tests
package com.demo.app.api.core.services.user_pii;
import com.demo.app.api.core.model.user_pii.UserPiiData;
import com.demo.app.api.core.repository.user_pii.UserPiiDataRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
public class UserPIIDataServiceImplTest {
@Mock
private UserPiiDataRepository userPiiDataRepository;
@InjectMocks
private UserPIIDataServiceImpl userPIIDataService;
@BeforeEach
public void setup() {
MockitoAnnotations.openMocks(this);
}
@Test
public void testGetUserPiiData() {
// Arrange
Integer userId = 1;
Integer loanId = 101;
UserPiiData data1 = new UserPiiData();
data1.setUserId(userId);
data1.setLoanId(loanId);
data1.setFirstName("John");
UserPiiData data2 = new UserPiiData();
data2.setUserId(userId);
data2.setLoanId(loanId);
data2.setFirstName("Jane");
List<UserPiiData> mockResponse = Arrays.asList(data1, data2);
when(userPiiDataRepository.getUserPiiData(userId, loanId)).thenReturn(mockResponse);
// Act
List<UserPiiData> result = userPIIDataService.getUserPiiData(userId, loanId);
// Assert
assertEquals(2, result.size());
assertEquals("John", result.get(0).getFirstName());
assertEquals("Jane", result.get(1).getFirstName());
}
}
Here is the chat thread for building the service layer.
Final Step: REST Controller Generation
Having set up our foundational layers, it was time to build our REST Controller. For this, we leveraged a previously existing REST Controller sample as a base template. We provided ChatGPT with this sample alongside the service interface and the necessary entity object.
In response, ChatGPT generated a RestController
that was tailored to our requirements. Below are the generated classes and code:
package com.demo.app.api.controller.user_pii;
import com.demo.app.api.core.model.user_pii.UserPiiData;
import com.demo.app.api.core.services.user_pii.UserPIIDataService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@CrossOrigin(origins = "*")
@RestController
@RequestMapping("/api/mis/v1/ap")
@Api(tags = "Controller for fetching User's PII data")
public class UserPIIDataController {
@Autowired
UserPIIDataService userPIIDataService;
@GetMapping(path = "/pii-data/user/{userId}/loan/{loanId}", produces = "application/json")
@ApiOperation("Fetch user's PII data by user ID and loan ID")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successfully fetched user's PII data"),
@ApiResponse(code = 400, message = "Bad request"),
@ApiResponse(code = 404, message = "Not found"),
@ApiResponse(code = 500, message = "Internal server error")
})
public List<UserPiiData> getUserPiiDataByUserIdAndLoanId(
@PathVariable(name = "userId") Integer userId,
@PathVariable(name = "loanId") Integer loanId) {
return this.userPIIDataService.getUserPiiData(userId, loanId);
}
}
A standout feature of the auto-generated code was the inclusion of annotations, specifically ApiResponse
and ApiOperation
. Without ChatGPT's intervention, these essential annotations might have been inadvertently overlooked, which can affect the clarity and documentation quality of the API.
Subsequent to the REST Controller generation, we sought ChatGPT's assistance in producing unit test cases. Initially, the generated tests were designed as if they were calling the actual API. However, our intention was to strictly test the controller's code. After providing the necessary instructions to narrow down the focus, ChatGPT effectively delivered the tailored unit test code. Here's a snapshot of the generated unit test:
package com.demo.app.api.controller.user_pii;
import com.demo.app.api.core.model.user_pii.UserPiiData;
import com.demo.app.api.core.services.user_pii.UserPIIDataService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Collections;
import java.util.List;
import static org.mockito.Mockito.when;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class UserPIIDataControllerTest {
@Mock
private UserPIIDataService userPIIDataService;
@InjectMocks
private UserPIIDataController userPIIDataController;
@BeforeEach
public void setup() {
MockitoAnnotations.openMocks(this);
}
@Test
public void testGetUserPiiDataByUserIdAndLoanId() {
Integer userId = 1;
Integer loanId = 1;
UserPiiData userPiiData = new UserPiiData();
userPiiData.setUserId(userId);
userPiiData.setLoanId(loanId);
userPiiData.setFirstName("John");
userPiiData.setLastName("Doe");
List<UserPiiData> expectedUserPiiDataList = Collections.singletonList(userPiiData);
when(userPIIDataService.getUserPiiData(userId, loanId)).thenReturn(expectedUserPiiDataList);
List<UserPiiData> actualUserPiiDataList = userPIIDataController.getUserPiiDataByUserIdAndLoanId(userId, loanId);
assertEquals(expectedUserPiiDataList, actualUserPiiDataList);
}
}
Finally, to ensure everything was functioning as anticipated, we conducted a verification of the API endpoint using Postman. Delightfully, the API behaved exactly as expected, showcasing the practical applicability and precision of the auto-generated code. Here is the chat thread.
The above example might seem straightforward, but we've also applied this approach to build API endpoints for inserting data observing a consistent pattern. The real advantage shines through when dealing with extensive tables, say with 30 columns. Manually defining stored procedure parameters and constructing entities with a multitude of attributes — each requiring accurate column mapping — can be a tedious and error-prone process. However, leveraging tools like ChatGPT or other AI utilities eliminates these repetitive tasks. As a result, developers can produce more efficient, well-documented code with reduced effort.
Conclusion
The technological realm is evolving rapidly. With the advent of AI tools like ChatGPT, developers now possess powerful allies in their coding endeavors. By automating the more tedious and repetitive tasks in API development, these tools not only streamline the process but also drastically improve code quality and accuracy. The experiences shared in this article are a testament to the fact that AI's potential to revolutionize software development isn't mere speculation — it's a reality we're beginning to embrace. As we forge ahead, such collaborations between humans and machines will undoubtedly redefine the landscape of software engineering, opening doors to new possibilities and efficiency levels previously thought unattainable.
Opinions expressed by DZone contributors are their own.
Comments