Repository Layer
Directory: internal/repository/mysql/
The Repository Layer is part of the "Interface Adapters" circle in the Clean Architecture diagram. Its main purpose is to provide a concrete implementation of the repository interfaces defined in the Service Layer. This layer handles all the logic related to data persistence and retrieval from a specific data source, in this case, a MySQL database.
By isolating the data access logic here, the rest of the application remains agnostic to the database technology being used.
Key Components
1. The Repository Struct
The ArticleRepository struct holds a connection to the database (*sql.DB). This connection is injected when the repository is created, following the principle of dependency injection.
File: internal/repository/mysql/article.go
package mysql
// ... imports
type ArticleRepository struct {
Conn *sql.DB
}
// NewArticleRepository will create an object that represent the article.Repository interface
func NewArticleRepository(conn *sql.DB) *ArticleRepository {
return &ArticleRepository{conn}
}
2. Interface Implementation
This struct provides methods that implement the article.ArticleRepository interface. For example, here is the implementation for GetByID.
File: internal/repository/mysql/article.go
func (m *ArticleRepository) GetByID(ctx context.Context, id int64) (res domain.Article, err error) {
query := `SELECT id,title,content, author_id, updated_at, created_at
FROM article WHERE ID = ?`
list, err := m.fetch(ctx, query, id)
if err != nil {
return domain.Article{}, err
}
if len(list) > 0 {
res = list[0]
} else {
return res, domain.ErrNotFound
}
return
}
In this method:
- A SQL query is defined to select an article by its ID.
- A private helper method
fetchis called to execute the query and scan the results. - It handles the case where no article is found, returning a
domain.ErrNotFounderror. This is important because it translates a database-specific condition (sql.ErrNoRows, which is handled insidefetch) into a domain-level error, preventing the database details from leaking to the service layer.
3. Data Mapping
The fetch helper method is responsible for executing the query, iterating over the rows, and scanning the data from the database columns into the fields of the domain.Article struct.
File: internal/repository/mysql/article.go
func (m *ArticleRepository) fetch(ctx context.Context, query string, args ...interface{}) (result []domain.Article, err error) {
rows, err := m.Conn.QueryContext(ctx, query, args...)
// ... error handling
defer rows.Close()
result = make([]domain.Article, 0)
for rows.Next() {
t := domain.Article{}
authorID := int64(0)
err = rows.Scan(
&t.ID,
&t.Title,
&t.Content,
&authorID, // Scan author_id into a temporary variable
&t.UpdatedAt,
&t.CreatedAt,
)
// ... error handling
// Populate the nested Author struct
t.Author = domain.Author{
ID: authorID,
}
result = append(result, t)
}
return result, nil
}
This mapping is a core responsibility of the repository. It transforms the raw data from the database into the rich domain models that the rest of the application understands.