Hex Architecture & ChatGPT - Part 2, the first adapter
In this post, I’ll guide you through my interactions with ChatGPT-4, as it helps write and test the entire New York Times implementation of the news adapter we created in part 1. As a disclaimer, I don’t recommend using this approach if you’re working on sensitive projects or those involving intellectual property. Additionally, never blindly copy, paste, and execute any code generated by AI tools.
New York Times News Adapter
Googling to learn the best way of obtaining news content from New York Times is so 2022. Is there a public API or will I have to write a web scraper? Let ChatGPT figure it out!
Initial prompt
I added some documentation to the NewsAdapter port in an attempt to guide ChatGPT, and then copied what I thought was all relevant code it would need to perform this task.
I am creating an application in golang following the principals of hexagonal architecture. I would like for you to create an NewYorkTime news adapter that fits the following interface.
package ports import ( "context" "github.com/BaronBonet/content-generator/internal/core> /domain" ) // NewsAdapter interacts with external news services type NewsAdapter interface { // GetMainArticle finds the main article, the concept > of the main article will be adapter specific. GetMainArticle(ctx context.Context) (domain.> NewsArticle, error) } package domain import "time" type NewsArticle struct { Title string Body string Date Date } type Date struct { Day int Month time.Month Year int } This adapter will implement `GetMainArticle`.
ChatGPT responded with what appeared to be functional code (although it did use some depreciated methods) along with instructions on obtaining a NYTimes API key. I created an internal/adapters
directory and placed the provided code there.
Does it work?
I wanted to test this with my debugger and know if it actually works, so I asked chatGPT to wire it up.
thanks, can you now wire this adapter up for me in a main.go file, so I can test this in my goland debugger
After changing a few imports in the adapter and adding the main function it wrote to cmd/debugger/main.go
code, I tested the code in my goland debugger, and it worked!
Prompt ChatGPT to write the tests
We’re on a roll, lets finish this off by asking ChatGPT to make a test for this. I’ve noticed it’s better if you send as much information as possible, that’s why I copy/pasted all the adapter code, that ChatGPT wrote (although I did make a few changes).
It works great! Could you now write some test for this > adapter, you should only have to mock this line.
resp, err := http.DefaultClient.Do(req)
package adapters import ( "context" "encoding/json" "errors" "fmt" "io/ioutil" "net/http" "time" "github.com/BaronBonet/content-generator/internal/core> /domain" "github.com/BaronBonet/content-generator/internal/core> /ports" ) const ( apiURL = "https://api.nytimes.> com/svc/topstories/v2/home.json?api-key=%s" ) type nyTimesAdapter struct { apiKey string } func NewNYTimesNewsAdapter(apiKey string) ports.> NewsAdapter { return &nyTimesAdapter{apiKey: apiKey} } func (n *nyTimesAdapter) GetMainArticle(ctx context.> Context) (domain.NewsArticle, error) { url := fmt.Sprintf(apiURL, n.apiKey) req, err := http.NewRequestWithContext(ctx, http.> MethodGet, url, nil) if err != nil { return domain.NewsArticle{}, err } resp, err := http.DefaultClient.Do(req) if err != nil { return domain.NewsArticle{}, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return domain.NewsArticle{}, errors.New("failed to> fetch data from New York Times API") } body, err := ioutil.ReadAll(resp.Body) if err != nil { return domain.NewsArticle{}, err } var apiResponse NYTApiResponse err = json.Unmarshal(body, &apiResponse) if err != nil { return domain.NewsArticle{}, err } if len(apiResponse.Results) == 0 { return domain.NewsArticle{}, errors.New("no > articles found") } mainArticle := apiResponse.Results[0] date, err := time.Parse(time.RFC3339, mainArticle.> PublishedDate) if err != nil { return domain.NewsArticle{}, err } return domain.NewsArticle{ Title: mainArticle.Title, Body: mainArticle.Abstract, Date: domain.Date{ Day: date.Day(), Month: date.Month(), Year: date.Year(), }, }, nil } type NYTApiResponse struct { Results []NYTArticle `json:"results"` } type NYTArticle struct { Title string `json:"title"` Abstract string `json:"abstract"` PublishedDate string `json:"published_date"` }
ChatGPT’s Tests
I’ve included the start and end of the response ChatGPT made the entire test is here. I like how it decided to slightly change the implementation of the adapter, by making the NYTime adapter depend on an httpClient interface, to make it more testable.
…
Conclusion
We’ve now implemented the first of four adapters, with the following workflow:
- Document the adapter port we want implemented
- Copy the documented port and any other relevant code to ChatGPT
- Test that the code ChatGPT wrote works functionally through the debugger. Note: I use this step because ChatGPT sometime hallucinates and uses public methods that don’t exist or do the wrong thing.
- Write unit tests for the adapter.
I’ll use this same workflow to implement the other 3 adapters.
Current state of the project
.
├── cmd
│ └── debugger
│ └── main.go
├── go.mod
└── internal
├── adapters
│ ├── newsadapter_nytimes.go
│ └── newsadapter_nytimes_test.go
└── core
├── domain
│ └── domain.go
├── ports
│ └── driven.go
│ └── infrastructure.go
│ └── service.go
└── service
└── service.go
└── service_test.go