diff --git a/.gitignore b/.gitignore index 7683e8a..eb45c15 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ +.idea vendor/ article_clean _* diff --git a/app/main.go b/app/main.go index edf7a87..2dd3fbd 100644 --- a/app/main.go +++ b/app/main.go @@ -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" ) @@ -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) } @@ -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 diff --git a/internal/repository/mysql/article_test.go b/internal/repository/mysql/article_test.go index decd629..ba97ef3 100644 --- a/internal/repository/mysql/article_test.go +++ b/internal/repository/mysql/article_test.go @@ -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) @@ -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) @@ -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)) @@ -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) @@ -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)) @@ -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)) diff --git a/internal/rest/article_test.go b/internal/rest/article_test.go index 3a2f015..26a883b 100644 --- a/internal/rest/article_test.go +++ b/internal/rest/article_test.go @@ -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() @@ -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() @@ -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{ @@ -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, @@ -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{ diff --git a/article/service.go b/service/article_services_impl.go similarity index 74% rename from article/service.go rename to service/article_services_impl.go index 014d73d..2c22e90 100644 --- a/article/service.go +++ b/service/article_services_impl.go @@ -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 diff --git a/article/mocks/ArticleRepository.go b/service/mocks/ArticleRepository.go similarity index 100% rename from article/mocks/ArticleRepository.go rename to service/mocks/ArticleRepository.go diff --git a/article/mocks/AuthorRepository.go b/service/mocks/AuthorRepository.go similarity index 100% rename from article/mocks/AuthorRepository.go rename to service/mocks/AuthorRepository.go diff --git a/service/service.go b/service/service.go new file mode 100644 index 0000000..c693585 --- /dev/null +++ b/service/service.go @@ -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, + } +} diff --git a/article/service_test.go b/service/service_test.go similarity index 88% rename from article/service_test.go rename to service/service_test.go index e41d137..5da9c4d 100644 --- a/article/service_test.go +++ b/service/service_test.go @@ -1,4 +1,4 @@ -package article_test +package service_test import ( "context" @@ -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) { @@ -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) @@ -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) @@ -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) @@ -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) @@ -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) @@ -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) @@ -160,7 +160,7 @@ 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) @@ -168,11 +168,11 @@ func TestDelete(t *testing.T) { 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) @@ -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) @@ -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)