Delivery Layer (REST API)
Directory: internal/rest/
The Delivery Layer is the outermost layer of the application. It is responsible for handling interactions with the outside world. In this project, the delivery mechanism is a REST API that exposes the application's functionality over HTTP.
This layer depends on the Service Layer to perform business operations, and it is responsible for:
- Defining API endpoints (routes).
- Parsing and validating incoming HTTP requests.
- Calling the appropriate service methods with the parsed data.
- Formatting the data returned by the service into an HTTP response (typically JSON).
- Mapping domain/service errors to appropriate HTTP status codes.
This project uses the Echo web framework, but thanks to the decoupled architecture, it could be replaced with another framework with minimal effort.
Key Components
1. The ArticleHandler
The ArticleHandler struct holds a dependency on the ArticleService interface. This allows it to call the business logic methods without being coupled to the service's implementation details.
File: internal/rest/article.go
// ArticleService represent the article's usecases
type ArticleService interface {
Fetch(ctx context.Context, cursor string, num int64) ([]domain.Article, string, error)
GetByID(ctx context.Context, id int64) (domain.Article, error)
// ... other methods
}
// ArticleHandler represent the httphandler for article
type ArticleHandler struct {
Service ArticleService
}
2. Route Registration
The NewArticleHandler function initializes the handler and registers the API routes with the Echo instance.
File: internal/rest/article.go
// NewArticleHandler will initialize the articles/ resources endpoint
func NewArticleHandler(e *echo.Echo, svc ArticleService) {
handler := &ArticleHandler{
Service: svc,
}
e.GET("/articles", handler.FetchArticle)
e.POST("/articles", handler.Store)
e.GET("/articles/:id", handler.GetByID)
e.DELETE("/articles/:id", handler.Delete)
}
3. Handler Method Implementation
Each handler method is responsible for a single endpoint. For example, FetchArticle handles requests to GET /articles.
File: internal/rest/article.go
// FetchArticle will fetch the article based on given params
func (a *ArticleHandler) FetchArticle(c echo.Context) error {
// 1. Parse and validate query parameters
numS := c.QueryParam("num")
num, err := strconv.Atoi(numS)
if err != nil || num == 0 {
num = defaultNum
}
cursor := c.QueryParam("cursor")
ctx := c.Request().Context()
// 2. Call the service layer
listAr, nextCursor, err := a.Service.Fetch(ctx, cursor, int64(num))
if err != nil {
// 3. Handle errors and map to status codes
return c.JSON(getStatusCode(err), ResponseError{Message: err.Error()})
}
// 4. Format and send the successful response
c.Response().Header().Set(`X-Cursor`, nextCursor)
return c.JSON(http.StatusOK, listAr)
}
This function clearly shows the responsibilities of the handler: parse, call, handle error, and respond.
4. Error to Status Code Mapping
A helper function getStatusCode translates the domain-specific errors returned by the service into standard HTTP status codes. This keeps the error handling logic consistent across all handlers.
File: internal/rest/article.go
func getStatusCode(err error) int {
if err == nil {
return http.StatusOK
}
logrus.Error(err)
switch err {
case domain.ErrInternalServerError:
return http.StatusInternalServerError
case domain.ErrNotFound:
return http.StatusNotFound
case domain.ErrConflict:
return http.StatusConflict
default:
return http.StatusInternalServerError
}
}