Using ChatGPT to Help Developers in Their Daily Work: Writing a Spring Boot App Together

Written by KonstantinGlumov | Published 2023/08/29
Tech Story Tags: web-development | java | kotlin | chatgpt | openai | chatgpt-guide | software-development | guide

TLDRvia the TL;DR App

ChatGPT is becoming more and more like an indispensable tool that helps the developer automate some actions in the daily routine. He becomes a companion, focusing on top-level and architectural tasks. How a calculator or Excel once significantly simplified the work of accountants and office staff.

In this article, I want to show by example how ChatGPT can help a developer right now. We will make an application and then improve it. All this will take place in a dialogue with an electronic assistant.

The structure of the request to ChatGPT may include not only the question itself but also three additional parameters. This is a role, additional information, and clarification. They are not mandatory, but they can give us more relevant information and get accurate answers instead of vague formulations. It's as if we are communicating with a real specialist, deeply immersed in the problem, and not a copywriter.

Imagine that we were tasked with creating an API for obtaining cryptocurrency quotes from external sources. The application has to be written using modern technologies, have high-quality code, and be reliable without a bottleneck.

There is one point to consider here. The ChatGPT database has information only until September 2021. If we ask to write code on technologies that came out after, we will receive a similar message:

We will form a request in which we will also specify the role and additional information.

You are a professional software engineer

Write me an application that will receive bitcoin quotes from external sources

Use Spring Boot, java 17 and gradle

ChatGPT tells us step by step what needs to be done:

  • Create a Spring Boot project through Spring Initializer or IDE

  • project structure

  • classes with code

  • main Spring Boot application class

  • publishes build.gradle file with required dependencies

  • a command with which we can launch the application

  • The endpoint address by which we can reach it via HTTP

    In accordance with best practices, it divides logic into layers of model, service, and controller. It gives the names of classes and variables under the conventions of Java style.

public class Quote {
    private String symbol;
    private double price;
    // Constructors, getters, setters 
}
@Service
public class QuoteService {
    private static final String QUOTE_API_URL = "https://api.example.com/bitcoin-quotes"; // Replace with actual API URL 

    public Quote getBitcoinQuote() {
        RestTemplate restTemplate = new RestTemplate();
        Quote quote = restTemplate.getForObject(QUOTE_API_URL, Quote.class);
        return quote;
    }
}
@RestController
public class QuoteController {
    @Autowired
    private QuoteService quoteService;

    @GetMapping("/bitcoin-quote")
    public Quote getBitcoinQuote() {
        return quoteService.getBitcoinQuote();
    }
}

Since we need reliability, one data source may not be enough. Let's ask ChatGPT to provide us with actual information from two data sources. Do not forget that the addresses may have changed since the database was filled in 2021.

Implement the getBitcoinQuote method. It should receive information from two actual data sources

@Service
public class QuoteService {
    private static final String SOURCE_1_API_URL = "https://api.coindesk.com/v1/bpi/currentprice/BTC.json";
    private static final String SOURCE_2_API_URL = "https://api.blockchain.info/ticker";

    public Quote getBitcoinQuote() {
        RestTemplate restTemplate = new RestTemplate();
        Quote quoteFromSource1 = restTemplate.getForObject(SOURCE_1_API_URL, Quote.class);
        Quote quoteFromSource2 = restTemplate.getForObject(SOURCE_2_API_URL, Quote.class);
        Quote selectedQuote = chooseQuote(quoteFromSource1, quoteFromSource2);
        return selectedQuote;
    }

    private Quote chooseQuote(Quote quote1, Quote quote2) {
        // Example logic: Choose the quote with the higher price 
        if (quote1.getPrice() > quote2.getPrice()) {
            return quote1;
        } else {
            return quote2;
        }
    }
}

ChatGPT has added the chooseQuote method with the logic of choosing a quote. It made it private since it is not called from other methods. Later, we will be able to change it in our sense, for example, to take only a non-null value if suddenly one of the data sources becomes unavailable.

We are not satisfied with receiving quotes sequentially, as this slows down the application. Let's ask ChatGPT to convert calls to parallel calls and use the technology we need - reactive.


Make getting quotes from external sources not sequentially, but in parallel with help of reactive

To do this, we need to replace dependencies. What the bot immediately warns us about.

Our getBitcoinQuote method in service has also changed.

    public Mono<Quote> getBitcoinQuote() {
        RestTemplate restTemplate = new RestTemplate();
        Mono<Quote> quoteFromSource1 = Mono.fromCallable(() -> restTemplate.getForObject(SOURCE_1_API_URL, Quote.class));
        Mono<Quote> quoteFromSource2 = Mono.fromCallable(() -> restTemplate.getForObject(SOURCE_2_API_URL, Quote.class));
        return Mono.zip(quoteFromSource1, quoteFromSource2, this::chooseQuote);
    }

We will give the response as a Mono.

@RestController
public class QuoteController {
    @Autowired
    private QuoteService quoteService;

    @GetMapping("/bitcoin-quote")
    public Mono<Quote> getBitcoinQuote() {
        return quoteService.getBitcoinQuote();
    }
}

We also get a detailed explanation of what was done. This can be very useful for developers, especially when they are working with a new technology and do not fully understand all the nuances of working with it.

To understand that we have written the application correctly and the logic works out in the way we intended, it is necessary to write tests. We will ask you to do this using the framework.

Write tests for our application with SpringBootTest

As a result, we get two tests - one integration covering our controller and one for the service. ChatGPT generated the test data itself and used the correct approach with MockMvc to test the controller. It also provides a brief reference on how the @SpringBootTest annotation and mocks via @MockBean works. Everything is convenient and clear—no need to Google again if you forgot something.

@SpringBootTest
public class QuoteServiceIntegrationTest {
    @Autowired
    private QuoteService quoteService;
    @MockBean
    private RestTemplate restTemplate;

    @Test
    public void testGetBitcoinQuote() {
        Quote quote = new Quote("BTC", 52000.0);
        when(restTemplate.getForObject(QuoteService.SOURCE_1_API_URL, Quote.class)).thenReturn(quote);
        when(restTemplate.getForObject(QuoteService.SOURCE_2_API_URL, Quote.class)).thenReturn(quote);
        Mono<Quote> result = quoteService.getBitcoinQuote();
        StepVerifier.create(result).expectNextMatches(q -> q.getPrice() == 52000.0).verifyComplete();
    }
}
@SpringBootTest
@AutoConfigureMockMvc
public class QuoteControllerIntegrationTest {
    @Autowired
    private MockMvc mockMvc;
    @MockBean
    private QuoteService quoteService;

    @Test
    public void testGetBitcoinQuote() throws Exception {
        Quote quote = new Quote("BTC", 52000.0);
        when(quoteService.getBitcoinQuote()).thenReturn(Mono.just(quote));
        mockMvc.perform(get("/bitcoin-quote")
                .accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$.symbol").value("BTC"))
                .andExpect(jsonPath("$.price").value(52000.0));
    }
}

We see that all test data is generated correctly.

It also provides information about which dependencies we need to add to the project.

        dependencies {
            // ... other dependencies 
            
            // Testing dependencies 
            testImplementation 'org.springframework.boot:spring-boot-starter-test' 
            testImplementation 'io.projectreactor:reactor-test' 
            testImplementation 'org.mockito:mockito-core' 
            testImplementation 'org.springframework.boot:spring-boot-starter-webflux' 
            testImplementation 'org.springframework.boot:spring-boot-starter-data-jpa' 
            testImplementation 'org.springframework.boot:spring-boot-starter-data-redis' 
            testImplementation 'org.springframework.boot:spring-boot-starter-security' 
            testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0' 
            testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
        }

We have a ready API for getting quotes. We need to describe and document it so that external users or developers who will finalize the project can get all the necessary information.

Make documentation for the api

ChatGPT suggests doing this through Swagger, which is the industry standard. Also, it recommends adding a dependency to our build.gradle file, register Bean for configuration.

@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.yourpackage.controller"))
                .paths(PathSelectors.any())
                .build();
    }
}

It adds the necessary annotations to the controller.

@RestController
@RequestMapping("/api")
@Api(tags = "Bitcoin Quote API")
public class QuoteController {
    @Autowired
    private QuoteService quoteService;

    @GetMapping("/bitcoin-quote")
    @ApiOperation("Get a Bitcoin quote")
    public Mono<Quote> getBitcoinQuote() {
        return quoteService.getBitcoinQuote();
    }
}

And it says at which address we will be able to access the Swagger UI when launching our Spring Boot application.

http://localhost:8080/swagger-ui.html

And what to do if we want to rewrite our application into another programming language. Many companies are now rewriting their microservices from Java to Kotlin since they work on the same platform. In addition, Kotlin is more concise and understandable to developers and has some advantages, such as the convenience of working with asynchronous code. Let's ask ChatGPT to rewrite our application on Kotlin.

Rewrite to Kotlin and coroutines

We get a new project structure with updated dependencies and our Kotlin code. ChatGPT also provides brief information on coroutines.

@Service
class QuoteService {
    private val SOURCE_1_API_URL = "https://api.coindesk.com/v1/bpi/currentprice/BTC.json"
    private val SOURCE_2_API_URL = "https://api.blockchain.info/ticker"

    suspend fun getBitcoinQuote(): Quote {
        val restTemplate = RestTemplate()
        val quoteFromSource1 = getQuoteAsync(restTemplate, SOURCE_1_API_URL)
        val quoteFromSource2 = getQuoteAsync(restTemplate, SOURCE_2_API_URL)
        val (quote1, quote2) = listOf(
            quoteFromSource1,
            quoteFromSource2
        ).awaitAll()
        return chooseQuote(quote1, quote2)
    }

    private suspend fun getQuoteAsync(restTemplate: RestTemplate, url: String): Quote {
        return kotlin.coroutines.suspendCoroutine { continuation ->
            val quote = restTemplate.getForObject(url, Quote::class.java)
            continuation.resume(quote!!)
        }
    }

    private fun chooseQuote(quote1: Quote, quote2: Quote): Quote {
        // Example logic: Choose the quote with the higher price
        return if (quote1.price > quote2.price) {
            quote1
        } else {
            quote2
        }
    }
}

@RestController
class QuoteController @Autowired constructor(private val quoteService: QuoteService) {
    @GetMapping("/bitcoin-quote")
    suspend fun getBitcoinQuote(): Quote {
        return quoteService.getBitcoinQuote()
    }
}

Today, ChatGPT can be a full-fledged assistant for developers. It can:

  • Create an application skeleton

  • Write business logic according to the terms of reference

  • Use the necessary technologies

  • write tests

  • generate test data

  • migrate a project from one programming language to another

By learning how to interact with ChatGPT, you can reduce the time to perform routine tasks and focus on more interesting ones. Of course, he is still not able to replace the programmer; his code needs to be refactored and adjusted to suit himself. For example, it can take into account all corner cases. Also, beginners should not mindlessly engage in copy-paste (as with Stackoverflow).


Written by KonstantinGlumov | Software engineer
Published by HackerNoon on 2023/08/29