Use Chatgpt to create a webhook microservice
Published: 2023-04-29 04:28:16

## Introduction

ChatGPT is a large language model that has been trained on a massive dataset of human language, allowing it to understand and generate natural language. One of the most exciting applications of ChatGPT is in programming, where it can automate the process of generating code. In this article, we will explore how good ChatGPT is at writing code and the potential benefits and limitations of using ChatGPT to generate programming code.

---

## Let's get started
Let's try with a really long and complex reqirement prompt.

> 
Please act as a senior backend developer and write a webhook microservice in Java using the Spring Boot Framework. Please do not skip any implementations inside method. The program should be easy to maintain with high readability and expandability. No TODO should be written in the code.
> 
The microservice will be a webhook service that will be managed by Gradle. The API endpoints will follow the design standards of a REST API. The microservice will have two entities: Session and History. The Session entity will represent a webhook session, which will have a unique ID in UUID format. The History entity will represent a request made to this webhook session,including the request time, URL, and HTTP method. The History entity will have a many-to-one relationship with the Session entity.
> 
The microservice will expose the following endpoints:
> 
A "webhook" endpoint in POST method to create a new webhook session. This endpoint will generate a new UUID.
A "webhook/{uuid}/config" endpoint to allow the user to configure what response should be returned in the "webhook" endpoint based on the regular expression string. This endpoint will accept a regular expression string and a jsonNode. A session can have multiple pairs sof regular expression and response.
A "webhook/{uuid}" endpoint to receive HTTP requests, save them into MongoDB, and return a response matched the first regular expression based on the request URL in the "configure" endpoint in JSON. This endpoint will accept any kind of HTTP method. If the user does not configure the response, the default JSON response will be "{'status': 'success'}".
A "webhook/{uuid}/history" endpoint to allow the user to search all webhook records.
> 
Use H2 Database.
> 
A schueduler should be set to housekeep the data periodically. A webhook session should be kept for one week only, and all child histories under the session should be removed as well.
> 
Please follow the programming style of Domain Driven Design and Test Driven Design. Using Lombok and annotation is highly encouraged.
> 

---

## Result

Sure, here's an implementation of the webhook microservice in Java using the Spring Boot framework:

Session Entity:

```
@Entity
@Data
@NoArgsConstructor
public class Session {

    @Id
    private UUID id;
    @OneToMany(mappedBy = "session", cascade = CascadeType.ALL)
    private List<History> histories;

    public Session(UUID id) {
        this.id = id;
    }
}
```
History Entity:

```
@Entity
@Data
@NoArgsConstructor
public class History {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String url;
    private String method;
    private LocalDateTime requestTime;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "session_id")
    private Session session;

    public History(String url, String method, LocalDateTime requestTime, Session session) {
        this.url = url;
        this.method = method;
        this.requestTime = requestTime;
        this.session = session;
    }
}
```

SessionRepository:

```
@Repository
public interface SessionRepository extends JpaRepository<Session, UUID> {

}
```

HistoryRepository:

```
@Repository
public interface HistoryRepository extends JpaRepository<History, Long> {

    List<History> findBySessionId(UUID sessionId);
}
```
WebhookController:

```
@RestController
@RequestMapping("/webhook")
public class WebhookController {

    @Autowired
    private SessionRepository sessionRepository;
    @Autowired
    private HistoryRepository historyRepository;
    @Autowired
    private Scheduler scheduler;

    @PostMapping
    public ResponseEntity<UUID> createSession() {
        UUID sessionId = UUID.randomUUID();
        Session session = new Session(sessionId);
        sessionRepository.save(session);
        scheduler.scheduleSessionDeletion(sessionId);
        return ResponseEntity.ok(sessionId);
    }

    @PostMapping("/{uuid}/config")
    public ResponseEntity<Void> configureSession(@PathVariable("uuid") UUID sessionId,
                                                  @RequestParam("regex") String regex,
                                                  @RequestBody JsonNode response) {
        Optional<Session> optionalSession = sessionRepository.findById(sessionId);
        if (optionalSession.isPresent()) {
            Session session = optionalSession.get();
            session.addConfiguration(regex, response);
            sessionRepository.save(session);
            return ResponseEntity.ok().build();
        } else {
            return ResponseEntity.notFound().build();
        }
    }

    @RequestMapping(value = "/{uuid}", method = {RequestMethod.GET, RequestMethod.POST})
    public ResponseEntity<JsonNode> handleRequest(@PathVariable("uuid") UUID sessionId,
                                                   @RequestBody(required = false) String requestBody,
                                                   HttpServletRequest request) {
        Optional<Session> optionalSession = sessionRepository.findById(sessionId);
        if (optionalSession.isPresent()) {
            Session session = optionalSession.get();
            JsonNode response = session.getResponse(request.getRequestURI());
            History history = new History(request.getRequestURI(), request.getMethod(),
                    LocalDateTime.now(), session);
            historyRepository.save(history);
            return ResponseEntity.ok(response);
        } else {
            return ResponseEntity.notFound().build();
        }
    }

    @GetMapping("/{uuid}/history")
    public ResponseEntity<List<History>> getSessionHistory(@PathVariable("uuid") UUID sessionId) {
        List<History> histories = historyRepository.findBySessionId(sessionId);
        return ResponseEntity.ok(histories);
    }
}
```
SessionService:

```
@Service
public class SessionService {

    @Autowired
    private SessionRepository sessionRepository;

    public Session createSession() {
        UUID sessionId = UUID.randomUUID();
        Session session = new Session(sessionId);
        sessionRepository.save(session);
        return session;
    }

    public Optional<Session> getSession(UUID sessionId) {
        return sessionRepository.findById(sessionId);
    }

    public void configureSession(Session session, String regex, JsonNode response) {
        session.addConfiguration(regex, response);
        sessionRepository.save(session);
    }

    public JsonNode handleRequest(Session session, String uri) {
        return session.getResponse(uri);
    }
}
```
SessionScheduler:

```
@Component
public class SessionScheduler {

    @Autowired
    private SessionRepository sessionRepository;
    @Autowired
    private HistoryRepository historyRepository;

    @Scheduled(cron = "0 0 0 * * *")
    public void deleteExpiredSessions() {
        LocalDateTime expirationTime = LocalDateTime.now().minusWeeks(1);
        List<Session> expiredSessions = sessionRepository.findByCreatedBefore(expirationTime);
        for (Session session : expiredSessions) {
            historyRepository.deleteBySessionId(session.getId());
            sessionRepository.delete(session);
        }
    }
}
```
I hope this implementation meets your requirements. Let me know if you have any questions or need further assistance.

---
## Reviewing the Generated Code
I found that the generated code is syntactically correct, but some methods are missing in the code. That's why it's important to review the generated code carefully and thoroughly, as it's very possible that the code may contain errors, especailly asking ChatGpt to write the entire program.


---
## Debugging

Error message is prompted at start up: "Caused by: org.hibernate.type.descriptor.java.spi.JdbcTypeRecommendationException: Could not determine recommended JdbcType for com.fasterxml.jackson.databind.JsonNode", so I ask ChatGpt to debug. ChatGpt succesfully points out that the JsonNode field in Class "Configuration" cannot be determined.

So I request ChatGpt to fix the missing AttributeConverter for JsonNode, and the response is great.

```
@Converter
public class JsonNodeConverter implements AttributeConverter<JsonNode, String> {

    private static final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public String convertToDatabaseColumn(JsonNode jsonNode) {
        try {
            return objectMapper.writeValueAsString(jsonNode);
        } catch (JsonProcessingException e) {
            throw new IllegalArgumentException("Failed to convert JSON node to string", e);
        }
    }

    @Override
    public JsonNode convertToEntityAttribute(String json) {
        try {
            return objectMapper.readTree(json);
        } catch (IOException e) {
            throw new IllegalArgumentException("Failed to convert string to JSON node", e);
        }
    }
}
```
It seems that a shorter, well-defined and specific requirement prompt may be more effective in generating accurate code. Conversely, a longer requirement prompt is more challenging for ChatGPT to fully understand and may result in less accurate or less efficient code.

---

## Final thoughts

I do believe that ChatGPT's coding capabilities are constantly evolving through ongoing training and optimization. While at present, ChatGPT's coding capabilities may be limited to simpler tasks and it may struggle with more complex programming tasks.

Good at:

- Generating code snippets for specific tasks or functions
- Providing explanations for programming concepts or terms
- Identifying common syntax errors or bugs in simple code

Bad at:

- Debugging complex programs with multiple layers of logic
- Writing entire programs from scratch


ChatGPT can be a helpful tool for programmers, but it cannot fully replace human programmers. Instead, it can be used as a complementary tool for generating code, answering questions, and providing support for programming tasks.