GithubHelp home page GithubHelp logo

clean-architecture-go-v2's Introduction

Clean Architecture in Go

This old repository doesn't represent what I'm using nowadays. In 2023, I am using and recommending what my colleagues and I have described in this post

Build Status

Post: Clean Architecture, 2 years later

Build

make

Run tests

make test

API requests

Add book

curl -X "POST" "http://localhost:8080/v1/book" \
     -H 'Content-Type: application/json' \
     -H 'Accept: application/json' \
     -d $'{
  "title": "I Am Ozzy",
  "author": "Ozzy Osbourne",
  "pages": 294,
  "quantity":10
}'

Search book

curl "http://localhost:8080/v1/book?title=ozzy" \
     -H 'Content-Type: application/json' \
     -H 'Accept: application/json'

Show books

curl "http://localhost:8080/v1/book" \
     -H 'Content-Type: application/json' \
     -H 'Accept: application/json'

Add user

curl -X "POST" "http://localhost:8080/v1/user" \
     -H 'Content-Type: application/json' \
     -H 'Accept: application/json' \
     -d $'{
  "email": "[email protected]",
  "first_name": "Ozzy",
  "last_name": "Osbourne",
  "password": "bateater666"
}'

Search user

curl "http://localhost:8080/v1/user?name=ozzy" \
     -H 'Content-Type: application/json' \
     -H 'Accept: application/json'

Show users

curl "http://localhost:8080/v1/user" \
     -H 'Content-Type: application/json' \
     -H 'Accept: application/json'

Borrow a book

curl "http://localhost:8080/v1/loan/borrow/be8b1757-b043-4dbd-b873-63fa9ecd8bb1/282885d7-5d5e-4205-87eb-edc2b2ac5022" \
     -H 'Content-Type: application/json' \
     -H 'Accept: application/json'

Return a book

curl "http://localhost:8080/v1/loan/return/be8b1757-b043-4dbd-b873-63fa9ecd8bb1" \
     -H 'Content-Type: application/json' \
     -H 'Accept: application/json'

CMD

Search for a book

./bin/search ozzy

clean-architecture-go-v2's People

Contributors

devadomas avatar eminetto avatar mahfuz110244 avatar rshmhrj avatar stickler-ci avatar thecodenation avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

clean-architecture-go-v2's Issues

Invalid memory access on book validator fail

Hey,

First of all, nice work, I really loved your implementation clean architecture, since I'm huge fan of Uncle Bob's.
I do implement and follow those rulesets in my node projects.

Anyway, onto the problem.
F.e. when making request with an invalid quantity value, validator throws an error.
Error is handled on service, but in incorrect way.

Code snipped from area of issue:

func (s *Service) CreateBook(title string, author string, pages int, quantity int) (entity.ID, error) {
	b, err := entity.NewBook(title, author, pages, quantity)
	if err != nil {
		return b.ID, err
	}
	return s.repo.Create(b)
}

When validator throws an error, it sets return values to nil, error, then on service function tries to access unaccessible area(nil in our case) and throws runtime error.

This can be fixed by not accessing unaccessible element(b.ID).

Cheers, Adam.

Is using `newInmem` only used for testing?

Since you are using stdlib map, in inmem.go inside use cases, I was wondering if this is ever relied on inside of the HTTP Handlers. If so, this would not be thread safe since HTTP Handlers are completely concurrent in go https://golang.org/doc/faq#atomic_maps.

If this is inded the case and you need a solution, you could use sync.Map which is thread safe.

Great job

I'd like to congrat you, Elton. It's the most intuitive fundamental example of Clean Arch I've ever seen. Great job, man.

What does ops folder use for?

Hi, thanks for great implementation.
Can you explain what does the ops folder use for?
And also as a recommendation, you can add log folder too.

log.Fatal call on main

Hi @eminetto,

It is correct using log.Fatal on your main? Since it calls os.Exit and it will never run your defer functions (gracefully closing your database connection).

Clean architecture folder structure

(Note: Newbie to Go here, so maybe all I'm saying does not apply (OO-mindset, etcetera))

Hi @eminetto

I was reading your Clean Architecture, 2 years later with interest and it struck me that I would create a different folder structure. I'd like your opinion on that.

In a hexagonal architecture - and also when you move to microservices - each (sub)domain would be entirely self-contained. You have Book core domain and User generic domain. In a production app these could grow quite large in feature set they support and there might be potentially many other (sub)domains, like e.g. Billing, Shipping, Reviews, Authors, etc.

Your clean architecture has one hexagon with all domains in the center (in domain/entity), and some mixing of concerns that I'd plance in different layers (repository impls like repository_inmem.go are part of 'infrastructure' layer).

What I was looking at in a Typescript project (with DDD and Command/Query segregation i.e. CQRS) was something like:

/core
  └-- /user (generic domain)
         ├-- /domain (inner layer)
         |      ├ user.go (aggregate root)
         |      ├ user_profile.go (entity)
         |      ├ email.go (value object)
         |      ├ password.go (value object)
         |      └ repository.go (repository interface)
         |      ├-- /event
         |      |      ├ user_created.go
         |      |      └ user_verified.go
         |      └-- /error
         |              ├ user_exists.go
         |              └ password_invalid.go
         ├-- /application
         |      ├-- /usecase
         |      |      ├-- /register_new_user
         |      |      |      ├ register_user.go (command)
         |      |      |      ├ register_user_test.go (BDD feature test)
         |      |      |      └ register_user_saga.go (saga)
         |      |      └-- /verify_user_email
         |      |              └ verify_user.go (command)
         |      └ user_service.go
         └-- /infrastructure
                 ├-- /storage
                 |      ├ repository_inmem.go
                 |      └ repository_mysql.go
                 └-- /api
                         └ users.go
/modules
  ├-- /book (core domain)
  |      └    ... same story here ...
  └-- /reviews (sub domain)
          └    ... and here ...

Of course I left lotsa stuff out, and probably Go has some root folder conventions. But the idea holds.

Grokking the Clean Architecture API setup process

Hi, excellent exposition of the Clean Architecture concepts - I'm deeply grateful for the articles and repo.

I'm using these as a pathway to learning (and teaching) API construction in Go, however I am having difficulty getting up the first couple of rungs of the knowledge ladder, and I imagine others will face the same difficulties.

I'd appreciate guidance on a few issues we've run into, if you don't mind.

[0] Is the code entirely functional (or merely illustrative in parts)?

[1] When running ./bin/api:
README describes API invocations with curl, however result is "Error adding book" or similar, and the api process log produces HTTP 500 errors:

[negroni] 2022-02-09T21:03:38+08:00 | 500 | 	 59.881538ms | localhost:8080 | GET /v1/book
[negroni] 2022-02-09T21:04:09+08:00 | 500 | 	 6.736978ms | localhost:8080 | POST /v1/book

I'll assume these are due to issue [2], but is there a simple way to expose more context?

[2] ./bin/search ozzy # produces:

2022/02/09 21:08:51 Error 1045: Access denied for user 'clean_architecture_go_v2'@'localhost'

... which indicates I don't have a database set up. Fair enough, but would have appreciated some guidance before the API curl examples.

[3] It also indicates I should authenticate somehow. Are we relying on HTTP/Basic?
Any clues you can offer?

[4] docker-compose.yml indicates a MySQL image might be used. I'll give that a try then.
./ops/db/init.sql seems to be used for database creation.
Passwords appear to be created in plain text, so can preload a few - correct?

[5] Are Grafana and Prometheus (a) functionally necessary or (b) valuable for understanding the code's behaviour?

Thx!

user.Validate failing when trying to borrow book (empty Password)

Hi @eminetto,
Thanks for putting this resource together!

I was following the README and got to the part for borrowing the newly added book with newly added user. Kept getting Error borrowing book which was caused by user.Validate throwing an ErrInvalidEntity because u.Password == "" resolves to true.

Removing that condition allows borrowing to take place:

// ./entity/user.go
//Validate validate data
func (u *User) Validate() error {
	if u.Email == "" || u.FirstName == "" || u.LastName == "" {
		return ErrInvalidEntity
	}

	return nil
}

The other option I can think of (which I don't like), would be to update the prepared statement in user_mysql.go to include the password:

func getUser(id entity.ID, db *sql.DB) (*entity.User, error) {
	stmt, err := db.Prepare(`select id, email, password, first_name, last_name, created_at from user where id = ?`)
	if err != nil {
		return nil, err
	}
	var u entity.User
	rows, err := stmt.Query(id)
	if err != nil {
		return nil, err
	}
	for rows.Next() {
		err = rows.Scan(&u.ID, &u.Email, &u.Password, &u.FirstName, &u.LastName, &u.CreatedAt)
	}
	stmt, err = db.Prepare(`select book_id from book_user where user_id = ?`)
	if err != nil {
		return nil, err
	}
	rows, err = stmt.Query(id)
	if err != nil {
		return nil, err
	}
	for rows.Next() {
		var i entity.ID
		err = rows.Scan(&i)
		u.Books = append(u.Books, i)
	}
	return &u, nil
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.