Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@

.idea
vendor/
article_clean
_*
Expand Down
5 changes: 3 additions & 2 deletions app/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import (

mysqlRepo "github.com/bxcodec/go-clean-arch/internal/repository/mysql"

"github.com/bxcodec/go-clean-arch/article"
"github.com/bxcodec/go-clean-arch/internal/rest"
"github.com/bxcodec/go-clean-arch/internal/rest/middleware"
"github.com/bxcodec/go-clean-arch/service"
"github.com/joho/godotenv"
)

Expand Down Expand Up @@ -45,6 +45,7 @@ func main() {
val.Add("loc", "Asia/Jakarta")
dsn := fmt.Sprintf("%s?%s", connection, val.Encode())
dbConn, err := sql.Open(`mysql`, dsn)
dbConn.SetConnMaxLifetime(2 * time.Second)
if err != nil {
log.Fatal("failed to open connection to database", err)
}
Expand Down Expand Up @@ -77,7 +78,7 @@ func main() {
articleRepo := mysqlRepo.NewArticleRepository(dbConn)

// Build service Layer
svc := article.NewService(articleRepo, authorRepo)
svc := service.NewService(articleRepo, authorRepo)
rest.NewArticleHandler(e, svc)

// Start Server
Expand Down
12 changes: 6 additions & 6 deletions internal/repository/mysql/article_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func TestFetchArticle(t *testing.T) {
AddRow(mockArticles[1].ID, mockArticles[1].Title, mockArticles[1].Content,
mockArticles[1].Author.ID, mockArticles[1].UpdatedAt, mockArticles[1].CreatedAt)

query := "SELECT id,title,content, author_id, updated_at, created_at FROM article WHERE created_at > \\? ORDER BY created_at LIMIT \\?"
query := "SELECT id,title,content, author_id, updated_at, created_at FROM service WHERE created_at > \\? ORDER BY created_at LIMIT \\?"

mock.ExpectQuery(query).WillReturnRows(rows)
a := articleMysqlRepo.NewArticleRepository(db)
Expand All @@ -57,7 +57,7 @@ func TestGetArticleByID(t *testing.T) {
rows := sqlmock.NewRows([]string{"id", "title", "content", "author_id", "updated_at", "created_at"}).
AddRow(1, "title 1", "Content 1", 1, time.Now(), time.Now())

query := "SELECT id,title,content, author_id, updated_at, created_at FROM article WHERE ID = \\?"
query := "SELECT id,title,content, author_id, updated_at, created_at FROM service WHERE ID = \\?"

mock.ExpectQuery(query).WillReturnRows(rows)
a := articleMysqlRepo.NewArticleRepository(db)
Expand Down Expand Up @@ -85,7 +85,7 @@ func TestStoreArticle(t *testing.T) {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}

query := "INSERT article SET title=\\? , content=\\? , author_id=\\?, updated_at=\\? , created_at=\\?"
query := "INSERT service SET title=\\? , content=\\? , author_id=\\?, updated_at=\\? , created_at=\\?"
prep := mock.ExpectPrepare(query)
prep.ExpectExec().WithArgs(ar.Title, ar.Content, ar.Author.ID, ar.CreatedAt, ar.UpdatedAt).WillReturnResult(sqlmock.NewResult(12, 1))

Expand All @@ -105,7 +105,7 @@ func TestGetArticleByTitle(t *testing.T) {
rows := sqlmock.NewRows([]string{"id", "title", "content", "author_id", "updated_at", "created_at"}).
AddRow(1, "title 1", "Content 1", 1, time.Now(), time.Now())

query := "SELECT id,title,content, author_id, updated_at, created_at FROM article WHERE title = \\?"
query := "SELECT id,title,content, author_id, updated_at, created_at FROM service WHERE title = \\?"

mock.ExpectQuery(query).WillReturnRows(rows)
a := articleMysqlRepo.NewArticleRepository(db)
Expand All @@ -122,7 +122,7 @@ func TestDeleteArticle(t *testing.T) {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}

query := "DELETE FROM article WHERE id = \\?"
query := "DELETE FROM service WHERE id = \\?"

prep := mock.ExpectPrepare(query)
prep.ExpectExec().WithArgs(12).WillReturnResult(sqlmock.NewResult(12, 1))
Expand Down Expand Up @@ -153,7 +153,7 @@ func TestUpdateArticle(t *testing.T) {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}

query := "UPDATE article set title=\\?, content=\\?, author_id=\\?, updated_at=\\? WHERE ID = \\?"
query := "UPDATE service set title=\\?, content=\\?, author_id=\\?, updated_at=\\? WHERE ID = \\?"

prep := mock.ExpectPrepare(query)
prep.ExpectExec().WithArgs(ar.Title, ar.Content, ar.Author.ID, ar.UpdatedAt, ar.ID).WillReturnResult(sqlmock.NewResult(12, 1))
Expand Down
14 changes: 7 additions & 7 deletions internal/rest/article_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func TestFetch(t *testing.T) {

e := echo.New()
req, err := http.NewRequestWithContext(context.TODO(),
echo.GET, "/article?num=1&cursor="+cursor, strings.NewReader(""))
echo.GET, "/service?num=1&cursor="+cursor, strings.NewReader(""))
assert.NoError(t, err)

rec := httptest.NewRecorder()
Expand All @@ -58,7 +58,7 @@ func TestFetchError(t *testing.T) {
mockUCase.On("Fetch", mock.Anything, cursor, int64(num)).Return(nil, "", domain.ErrInternalServerError)

e := echo.New()
req, err := http.NewRequestWithContext(context.TODO(), echo.GET, "/article?num=1&cursor="+cursor, strings.NewReader(""))
req, err := http.NewRequestWithContext(context.TODO(), echo.GET, "/service?num=1&cursor="+cursor, strings.NewReader(""))
assert.NoError(t, err)

rec := httptest.NewRecorder()
Expand Down Expand Up @@ -87,12 +87,12 @@ func TestGetByID(t *testing.T) {
mockUCase.On("GetByID", mock.Anything, int64(num)).Return(mockArticle, nil)

e := echo.New()
req, err := http.NewRequestWithContext(context.TODO(), echo.GET, "/article/"+strconv.Itoa(num), strings.NewReader(""))
req, err := http.NewRequestWithContext(context.TODO(), echo.GET, "/service/"+strconv.Itoa(num), strings.NewReader(""))
assert.NoError(t, err)

rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
c.SetPath("article/:id")
c.SetPath("service/:id")
c.SetParamNames("id")
c.SetParamValues(strconv.Itoa(num))
handler := rest.ArticleHandler{
Expand Down Expand Up @@ -129,7 +129,7 @@ func TestStore(t *testing.T) {

rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
c.SetPath("/article")
c.SetPath("/service")

handler := rest.ArticleHandler{
Service: mockUCase,
Expand All @@ -153,12 +153,12 @@ func TestDelete(t *testing.T) {
mockUCase.On("Delete", mock.Anything, int64(num)).Return(nil)

e := echo.New()
req, err := http.NewRequestWithContext(context.TODO(), echo.DELETE, "/article/"+strconv.Itoa(num), strings.NewReader(""))
req, err := http.NewRequestWithContext(context.TODO(), echo.DELETE, "/service/"+strconv.Itoa(num), strings.NewReader(""))
assert.NoError(t, err)

rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
c.SetPath("article/:id")
c.SetPath("service/:id")
c.SetParamNames("id")
c.SetParamValues(strconv.Itoa(num))
handler := rest.ArticleHandler{
Expand Down
37 changes: 2 additions & 35 deletions article/service.go → service/article_services_impl.go
Original file line number Diff line number Diff line change
@@ -1,47 +1,14 @@
package article
package service

import (
"context"
"time"

"github.com/bxcodec/go-clean-arch/domain"
"github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"

"github.com/bxcodec/go-clean-arch/domain"
)

// ArticleRepository represent the article's repository contract
//
//go:generate mockery --name ArticleRepository
type ArticleRepository interface {
Fetch(ctx context.Context, cursor string, num int64) (res []domain.Article, nextCursor string, err error)
GetByID(ctx context.Context, id int64) (domain.Article, error)
GetByTitle(ctx context.Context, title string) (domain.Article, error)
Update(ctx context.Context, ar *domain.Article) error
Store(ctx context.Context, a *domain.Article) error
Delete(ctx context.Context, id int64) error
}

// AuthorRepository represent the author's repository contract
//
//go:generate mockery --name AuthorRepository
type AuthorRepository interface {
GetByID(ctx context.Context, id int64) (domain.Author, error)
}

type Service struct {
articleRepo ArticleRepository
authorRepo AuthorRepository
}

// NewService will create a new article service object
func NewService(a ArticleRepository, ar AuthorRepository) *Service {
return &Service{
articleRepo: a,
authorRepo: ar,
}
}

/*
* In this function below, I'm using errgroup with the pipeline pattern
* Look how this works in this package explanation
Expand Down
File renamed without changes.
File renamed without changes.
38 changes: 38 additions & 0 deletions service/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package service

import (
"context"
"github.com/bxcodec/go-clean-arch/domain"
)

// ArticleRepository represent the article's repository contract
//
//go:generate mockery --name ArticleRepository
type ArticleRepository interface {
Fetch(ctx context.Context, cursor string, num int64) (res []domain.Article, nextCursor string, err error)
GetByID(ctx context.Context, id int64) (domain.Article, error)
GetByTitle(ctx context.Context, title string) (domain.Article, error)
Update(ctx context.Context, ar *domain.Article) error
Store(ctx context.Context, a *domain.Article) error
Delete(ctx context.Context, id int64) error
}

// AuthorRepository represent the author's repository contract
//
//go:generate mockery --name AuthorRepository
type AuthorRepository interface {
GetByID(ctx context.Context, id int64) (domain.Author, error)
}

type Service struct {
articleRepo ArticleRepository
authorRepo AuthorRepository
}

// NewService will create a new article service object
func NewService(a ArticleRepository, ar AuthorRepository) *Service {
return &Service{
articleRepo: a,
authorRepo: ar,
}
}
28 changes: 14 additions & 14 deletions article/service_test.go → service/service_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package article_test
package service_test

import (
"context"
Expand All @@ -8,9 +8,9 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"

"github.com/bxcodec/go-clean-arch/article"
"github.com/bxcodec/go-clean-arch/article/mocks"
"github.com/bxcodec/go-clean-arch/domain"
"github.com/bxcodec/go-clean-arch/service"
"github.com/bxcodec/go-clean-arch/service/mocks"
)

func TestFetchArticle(t *testing.T) {
Expand All @@ -32,7 +32,7 @@ func TestFetchArticle(t *testing.T) {
}
mockAuthorrepo := new(mocks.AuthorRepository)
mockAuthorrepo.On("GetByID", mock.Anything, mock.AnythingOfType("int64")).Return(mockAuthor, nil)
u := article.NewService(mockArticleRepo, mockAuthorrepo)
u := service.NewService(mockArticleRepo, mockAuthorrepo)
num := int64(1)
cursor := "12"
list, nextCursor, err := u.Fetch(context.TODO(), cursor, num)
Expand All @@ -51,7 +51,7 @@ func TestFetchArticle(t *testing.T) {
mock.AnythingOfType("int64")).Return(nil, "", errors.New("Unexpexted Error")).Once()

mockAuthorrepo := new(mocks.AuthorRepository)
u := article.NewService(mockArticleRepo, mockAuthorrepo)
u := service.NewService(mockArticleRepo, mockAuthorrepo)
num := int64(1)
cursor := "12"
list, nextCursor, err := u.Fetch(context.TODO(), cursor, num)
Expand Down Expand Up @@ -79,7 +79,7 @@ func TestGetByID(t *testing.T) {
mockArticleRepo.On("GetByID", mock.Anything, mock.AnythingOfType("int64")).Return(mockArticle, nil).Once()
mockAuthorrepo := new(mocks.AuthorRepository)
mockAuthorrepo.On("GetByID", mock.Anything, mock.AnythingOfType("int64")).Return(mockAuthor, nil)
u := article.NewService(mockArticleRepo, mockAuthorrepo)
u := service.NewService(mockArticleRepo, mockAuthorrepo)

a, err := u.GetByID(context.TODO(), mockArticle.ID)

Expand All @@ -93,7 +93,7 @@ func TestGetByID(t *testing.T) {
mockArticleRepo.On("GetByID", mock.Anything, mock.AnythingOfType("int64")).Return(domain.Article{}, errors.New("Unexpected")).Once()

mockAuthorrepo := new(mocks.AuthorRepository)
u := article.NewService(mockArticleRepo, mockAuthorrepo)
u := service.NewService(mockArticleRepo, mockAuthorrepo)

a, err := u.GetByID(context.TODO(), mockArticle.ID)

Expand All @@ -119,7 +119,7 @@ func TestStore(t *testing.T) {
mockArticleRepo.On("Store", mock.Anything, mock.AnythingOfType("*domain.Article")).Return(nil).Once()

mockAuthorrepo := new(mocks.AuthorRepository)
u := article.NewService(mockArticleRepo, mockAuthorrepo)
u := service.NewService(mockArticleRepo, mockAuthorrepo)

err := u.Store(context.TODO(), &tempMockArticle)

Expand All @@ -137,7 +137,7 @@ func TestStore(t *testing.T) {
mockAuthorrepo := new(mocks.AuthorRepository)
mockAuthorrepo.On("GetByID", mock.Anything, mock.AnythingOfType("int64")).Return(mockAuthor, nil)

u := article.NewService(mockArticleRepo, mockAuthorrepo)
u := service.NewService(mockArticleRepo, mockAuthorrepo)

err := u.Store(context.TODO(), &mockArticle)

Expand All @@ -160,19 +160,19 @@ func TestDelete(t *testing.T) {
mockArticleRepo.On("Delete", mock.Anything, mock.AnythingOfType("int64")).Return(nil).Once()

mockAuthorrepo := new(mocks.AuthorRepository)
u := article.NewService(mockArticleRepo, mockAuthorrepo)
u := service.NewService(mockArticleRepo, mockAuthorrepo)

err := u.Delete(context.TODO(), mockArticle.ID)

assert.NoError(t, err)
mockArticleRepo.AssertExpectations(t)
mockAuthorrepo.AssertExpectations(t)
})
t.Run("article-is-not-exist", func(t *testing.T) {
t.Run("service-is-not-exist", func(t *testing.T) {
mockArticleRepo.On("GetByID", mock.Anything, mock.AnythingOfType("int64")).Return(domain.Article{}, nil).Once()

mockAuthorrepo := new(mocks.AuthorRepository)
u := article.NewService(mockArticleRepo, mockAuthorrepo)
u := service.NewService(mockArticleRepo, mockAuthorrepo)

err := u.Delete(context.TODO(), mockArticle.ID)

Expand All @@ -184,7 +184,7 @@ func TestDelete(t *testing.T) {
mockArticleRepo.On("GetByID", mock.Anything, mock.AnythingOfType("int64")).Return(domain.Article{}, errors.New("Unexpected Error")).Once()

mockAuthorrepo := new(mocks.AuthorRepository)
u := article.NewService(mockArticleRepo, mockAuthorrepo)
u := service.NewService(mockArticleRepo, mockAuthorrepo)

err := u.Delete(context.TODO(), mockArticle.ID)

Expand All @@ -206,7 +206,7 @@ func TestUpdate(t *testing.T) {
mockArticleRepo.On("Update", mock.Anything, &mockArticle).Once().Return(nil)

mockAuthorrepo := new(mocks.AuthorRepository)
u := article.NewService(mockArticleRepo, mockAuthorrepo)
u := service.NewService(mockArticleRepo, mockAuthorrepo)

err := u.Update(context.TODO(), &mockArticle)
assert.NoError(t, err)
Expand Down