-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1944642
commit 62aa9cb
Showing
31 changed files
with
1,399 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
db_data |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
FROM golang:1.23 | ||
RUN apt update && apt upgrade -y && apt install -y git | ||
|
||
WORKDIR /go/src/app | ||
COPY app ./ | ||
RUN go mod tidy && go mod verify | ||
|
||
ENTRYPOINT [ "go", "run", "." ] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
--- | ||
title: Clean Code | ||
keywords: [clean, code, fiber, postgres, go] | ||
description: Implementing clean code in Go. | ||
--- | ||
|
||
# Clean Code Example | ||
|
||
[![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/clean-code) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/clean-code) | ||
|
||
This is an example of a RESTful API built using the Fiber framework (https://gofiber.io/) and PostgreSQL as the database. | ||
|
||
## Description of Clean Code | ||
|
||
Clean code is a philosophy and set of practices aimed at writing code that is easy to understand, maintain, and extend. Key principles of clean code include: | ||
|
||
- **Readability**: Code should be easy to read and understand. | ||
- **Simplicity**: Avoid unnecessary complexity. | ||
- **Consistency**: Follow consistent coding standards and conventions. | ||
- **Modularity**: Break down code into small, reusable, and independent modules. | ||
- **Testability**: Write code that is easy to test. | ||
|
||
This Fiber app is a good example of clean code because: | ||
|
||
- **Modular Structure**: The code is organized into distinct modules, making it easy to navigate and understand. | ||
- **Clear Separation of Concerns**: Different parts of the application (e.g., routes, handlers, services) are clearly separated, making the codebase easier to maintain and extend. | ||
- **Error Handling**: Proper error handling is implemented to ensure the application behaves predictably. | ||
|
||
## Start | ||
|
||
1. Build and start the containers: | ||
```sh | ||
docker compose up --build | ||
``` | ||
|
||
1. The application should now be running and accessible at `http://localhost:3000`. | ||
|
||
## Endpoints | ||
|
||
- `GET /api/v1/books`: Retrieves a list of all books. | ||
```sh | ||
curl -X GET http://localhost:3000/api/v1/books | ||
``` | ||
|
||
- `POST /api/v1/books`: Adds a new book to the collection. | ||
```sh | ||
curl -X POST http://localhost:3000/api/v1/books \ | ||
-H "Content-Type: application/json" \ | ||
-d '{"title":"Title"}' | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package main | ||
|
||
import ( | ||
"log/slog" | ||
"os" | ||
) | ||
|
||
// Configuration is used to store values from environment variables | ||
type Configuration struct { | ||
Port string | ||
DatabaseURL string | ||
} | ||
|
||
// NewConfiguration reads environment variables and returns a new Configuration | ||
func NewConfiguration() *Configuration { | ||
dbURL := getEnvOrDefault("DATABASE_URL", "") | ||
if dbURL == "" { | ||
slog.Warn("DATABASE_URL is not set") | ||
} | ||
return &Configuration{ | ||
Port: getEnvOrDefault("PORT", "3000"), | ||
DatabaseURL: dbURL, | ||
} | ||
} | ||
|
||
func getEnvOrDefault(key, defaultValue string) string { | ||
if value, exists := os.LookupEnv(key); exists { | ||
return value | ||
} | ||
return defaultValue | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package main | ||
|
||
import ( | ||
"os" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestNewConfiguration(t *testing.T) { | ||
os.Setenv("PORT", "8080") | ||
os.Setenv("DATABASE_URL", "postgres://user:pass@localhost:5432/dbname") | ||
defer os.Unsetenv("PORT") | ||
defer os.Unsetenv("DATABASE_URL") | ||
|
||
conf := NewConfiguration() | ||
|
||
assert.Equal(t, "8080", conf.Port) | ||
assert.Equal(t, "postgres://user:pass@localhost:5432/dbname", conf.DatabaseURL) | ||
} | ||
|
||
func TestNewConfiguration_Defaults(t *testing.T) { | ||
os.Unsetenv("PORT") | ||
os.Unsetenv("DATABASE_URL") | ||
|
||
conf := NewConfiguration() | ||
|
||
assert.Equal(t, "3000", conf.Port) | ||
assert.Equal(t, "", conf.DatabaseURL) | ||
} | ||
|
||
func TestGetEnvOrDefault(t *testing.T) { | ||
os.Setenv("TEST_ENV", "value") | ||
defer os.Unsetenv("TEST_ENV") | ||
|
||
value := getEnvOrDefault("TEST_ENV", "default") | ||
assert.Equal(t, "value", value) | ||
|
||
value = getEnvOrDefault("NON_EXISTENT_ENV", "default") | ||
assert.Equal(t, "default", value) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package datasources | ||
|
||
import "app/datasources/database" | ||
|
||
// DataSources is a struct that contains all the data sources | ||
// It is used to pass different data sources to the server and services | ||
type DataSources struct { | ||
DB database.Database | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package database | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"log/slog" | ||
"strings" | ||
) | ||
|
||
// Book represents a book in the database | ||
type Book struct { | ||
ID int | ||
Title string | ||
} | ||
|
||
// NewBook represents a new book to be created to the database | ||
type NewBook struct { | ||
Title string | ||
} | ||
|
||
// Database defines the interface for interacting with the book database. | ||
// Using this interface allows changing the implementation without affecting the rest of the code. | ||
type Database interface { | ||
// LoadAllBooks retrieves all books from the database. | ||
LoadAllBooks(ctx context.Context) ([]Book, error) | ||
|
||
// CreateBook adds a new book to the database. | ||
CreateBook(ctx context.Context, newBook NewBook) error | ||
|
||
// CloseConnections closes all open connections to the database. | ||
CloseConnections() | ||
} | ||
|
||
// NewDatabase creates a new Database instance | ||
func NewDatabase(ctx context.Context, databaseURL string) (Database, error) { | ||
if databaseURL == "" { | ||
slog.Info("Using in-memory database implementation") | ||
return newMemoryDB(), nil | ||
} | ||
|
||
if strings.HasPrefix(databaseURL, "postgres://") { | ||
db, err := newPostgresDB(ctx, databaseURL) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to initialize PostgreSQL database connection: %w", err) | ||
} | ||
slog.Info("Using PostgreSQL database implementation") | ||
return db, nil | ||
} | ||
|
||
return nil, fmt.Errorf("unsupported database URL scheme: %s", databaseURL) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package database | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/stretchr/testify/mock" | ||
) | ||
|
||
type DatabaseMock struct { | ||
mock.Mock | ||
} | ||
|
||
func (m *DatabaseMock) LoadAllBooks(ctx context.Context) ([]Book, error) { | ||
args := m.Called(ctx) | ||
if args.Get(0) == nil { | ||
return nil, args.Error(1) | ||
} | ||
return args.Get(0).([]Book), args.Error(1) | ||
} | ||
|
||
func (m *DatabaseMock) CreateBook(ctx context.Context, newBook NewBook) error { | ||
args := m.Called(ctx, newBook) | ||
return args.Error(0) | ||
} | ||
|
||
func (m *DatabaseMock) CloseConnections() { | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package database | ||
|
||
import ( | ||
"context" | ||
"reflect" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestNewDatabase_MemoryDB(t *testing.T) { | ||
ctx := context.Background() | ||
db, err := NewDatabase(ctx, "") | ||
assert.Nil(t, err) | ||
assert.Equal(t, "*database.memoryDB", reflect.TypeOf(db).String()) | ||
} | ||
|
||
func TestNewDatabase_PostgresDB(t *testing.T) { | ||
ctx := context.Background() | ||
db, err := NewDatabase(ctx, "postgres://localhost:5432") | ||
assert.Nil(t, err) | ||
assert.Equal(t, "*database.postgresDB", reflect.TypeOf(db).String()) | ||
} | ||
|
||
func TestNewDatabase_InvalidDatabaseConfiguration(t *testing.T) { | ||
ctx := context.Background() | ||
_, err := NewDatabase(ctx, "invalid") | ||
assert.ErrorContains(t, err, "unsupported database") | ||
} | ||
|
||
func assertBook(t *testing.T, book Book, expectedID int, expected NewBook) { | ||
assert.Equal(t, expectedID, book.ID) | ||
assert.Equal(t, expected.Title, book.Title) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package database | ||
|
||
import "context" | ||
|
||
// This is just an example and not for production use | ||
func newMemoryDB() Database { | ||
return &memoryDB{ | ||
records: make([]Book, 0, 10), | ||
idCounter: 0, | ||
} | ||
} | ||
|
||
type memoryDB struct { | ||
records []Book | ||
idCounter int | ||
} | ||
|
||
func (db *memoryDB) LoadAllBooks(_ context.Context) ([]Book, error) { | ||
return db.records, nil | ||
} | ||
|
||
func (db *memoryDB) CreateBook(_ context.Context, newBook NewBook) error { | ||
db.records = append(db.records, Book{ | ||
ID: db.idCounter, | ||
Title: newBook.Title, | ||
}) | ||
db.idCounter++ | ||
return nil | ||
} | ||
|
||
func (db *memoryDB) CloseConnections() { | ||
} |
44 changes: 44 additions & 0 deletions
44
example/clean-code/app/datasources/database/memory_db_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package database | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestMemoryDB_LoadBooks(t *testing.T) { | ||
db := newMemoryDB() | ||
books, err := db.LoadAllBooks(context.Background()) | ||
assert.Nil(t, err) | ||
assert.Equal(t, 0, len(books)) | ||
} | ||
|
||
func TestMemoryDB_SaveBook(t *testing.T) { | ||
db := newMemoryDB() | ||
newBook := NewBook{Title: "Title"} | ||
err := db.CreateBook(context.Background(), newBook) | ||
assert.Nil(t, err) | ||
|
||
books, err := db.LoadAllBooks(context.Background()) | ||
assert.Nil(t, err) | ||
assert.Equal(t, 1, len(books)) | ||
assertBook(t, books[0], 0, newBook) | ||
} | ||
|
||
func TestMemoryDB_SaveBookMultiple(t *testing.T) { | ||
db := newMemoryDB() | ||
newBook1 := NewBook{Title: "Title1"} | ||
err := db.CreateBook(context.Background(), newBook1) | ||
assert.Nil(t, err) | ||
|
||
newBook2 := NewBook{Title: "Title2"} | ||
err = db.CreateBook(context.Background(), newBook2) | ||
assert.Nil(t, err) | ||
|
||
books, err := db.LoadAllBooks(context.Background()) | ||
assert.Nil(t, err) | ||
assert.Equal(t, 2, len(books)) | ||
assertBook(t, books[0], 0, newBook1) | ||
assertBook(t, books[1], 1, newBook2) | ||
} |
Oops, something went wrong.