{"id":46735856,"url":"https://github.com/alexzh7/sample-service","last_synced_at":"2026-03-09T16:51:58.918Z","repository":{"id":57695157,"uuid":"481736122","full_name":"alexzh7/sample-service","owner":"alexzh7","description":"Golang CRUD service for DVD store that communicates by GRPC and follows the principles of Clean Architecture","archived":false,"fork":false,"pushed_at":"2022-05-05T23:49:36.000Z","size":2662,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-21T00:34:15.849Z","etag":null,"topics":["clean-architecture","docker-compose","example-project","go","golang","grpc","grpc-server","postgresql","sqlmock","testify","viper","zap"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alexzh7.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-04-14T20:10:34.000Z","updated_at":"2024-10-10T14:33:12.000Z","dependencies_parsed_at":"2022-09-02T08:50:47.759Z","dependency_job_id":null,"html_url":"https://github.com/alexzh7/sample-service","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/alexzh7/sample-service","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexzh7%2Fsample-service","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexzh7%2Fsample-service/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexzh7%2Fsample-service/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexzh7%2Fsample-service/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alexzh7","download_url":"https://codeload.github.com/alexzh7/sample-service/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexzh7%2Fsample-service/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30303053,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-09T14:33:48.460Z","status":"ssl_error","status_checked_at":"2026-03-09T14:33:48.027Z","response_time":61,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clean-architecture","docker-compose","example-project","go","golang","grpc","grpc-server","postgresql","sqlmock","testify","viper","zap"],"created_at":"2026-03-09T16:51:57.465Z","updated_at":"2026-03-09T16:51:58.896Z","avatar_url":"https://github.com/alexzh7.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Sample-service\nGolang CRUD service for DVD store that communicates by GRPC and follows the principles of [Clean Architecture](http://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html \"Clean Architecture\") by Robert Martin.\n\nIt has simplified business logic in order to concentrate on architecture, code organization and practicing GRPC.\n\n## Project structure\nProject structure (mostly) follows [Standard Go Project Layout](https://github.com/golang-standards/project-layout \"Standard Go Project Layout\").\n\n- `/cmd` - entry point of the app\n- `/config` - configuration\n- `/internal/dvdstore`  - application code (interfaces, transports, implementations)\n- `/internal/dvdstore/grpc` -  GRPC transport\n- `/internal/dvdstore/repository` - working with repositories, currently only postgresql\n- `/internal/dvdstore/usecase` - business logic\n- `/internal/models` - entities, exported errors, custom validations\n- `/internal/server` - initialization of the app (\"continues\" main.go)\n- `/pkg/postgres` - postgres connection config\n- `/proto` - protobuf definition and proto-generated code\n\nTo follow dependency inversion, use cases and repositories are described through interfaces.  \nConcrete repository implementations realize communication with needed data sources, in this project it is postgresql.  \nConcrete use case implementations aggregate repository interface; transport (grpc) aggregates use case interface.  \nSuch code organization simplifies unit testing and allows us to make code flexible - we can easily add/switch between data sources and transports, write different use cases.\n\n### Request processing logic\nTransport receives request from client and calls use case. Use case validates the request and calls repository. Repository retrieves data from data source and forms entity. Entity is mapped to response structure and returned to client with status code. If any error appears, the app returns corresponding error with error code.\n\n### DB schema\nProject uses [Dell DVD store](https://linux.dell.com/dvdstore/ \"Dell DVD store\") database:\n\n\u003cimg src=\"./db-schema.jpg\" alt=\"DB schema\" width=\"820\"/\u003e\n\nSome of the tables are ignored to simplify the business logic.\n\n## Running and usage\n```bash\n# Build and start container with database\ndocker compose up -d\n\n# Load dependencies\ngo mod tidy\n\n# Run the app\ngo run cmd/main.go\n```\n\nIf everything is ok, you will see this message: \n```bash\n{\"level\":\"info\",\"msg\":\"GRPC listening on port 9090\"}\n```\n\n### Usage\nYou can use any preferred GRPC client to call API, for example [grpcurl](https://github.com/fullstorydev/grpcurl \"grpcurl\") or [Postman](https://blog.postman.com/postman-now-supports-grpc/ \"Postman\").  \nService uses reflection, so you can describe it through the client.\n\n```bash\n# describe service\ngrpcurl -plaintext localhost:9090 describe\n\n# describe message \ngrpcurl -plaintext localhost:9090 describe proto.GetCustomerReq\n\n# request customer orders\ngrpcurl -d '{\"CustomerID\": 268}' -plaintext localhost:9090 proto.Dvdstore/GetCustomerOrders\n```\nThough I recommend to use Postman.\n## API methods\n\n- [Customers](#customers)\n  - [GetCustomers](#getcustomers)\n  - [GetCustomer](#getcustomer)\n  - [AddCustomer](#addcustomer)\n  - [DeleteCustomer](#deletecustomer)\n- [Products](#products)\n  - [GetProducts](#getproducts)\n  - [GetProduct](#getproduct)\n  - [AddProduct](#addproduct)\n  - [DeleteProduct](#deleteproduct)\n- [Orders](#orders)\n  - [GetOrder](#getorder)\n  - [GetCustomerOrders](#getcustomerorders)\n  - [AddOrder](#addorder)\n  - [DeleteOrder](#deleteorder)\n\n### Customers\n#### GetCustomers\nGetCustomers returns list of all Customers limited by provided limit\n\u003ctable\u003e\n\u003ctr\u003e \u003cth\u003e Request \u003c/th\u003e \u003cth\u003e Response \u003c/th\u003e \u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n  \n```json\n{\n    \"Limit\": 2\n}\n```\n  \n\u003c/td\u003e\n\u003ctd\u003e\n  \n```json\n{\n    \"CustomerList\": [\n        {\n            \"Id\": \"2\",\n            \"FirstName\": \"HQNMZH\",\n            \"LastName\": \"UNUKXHJVXB\",\n            \"Age\": \"80\"\n        },\n        {\n            \"Id\": \"3\",\n            \"FirstName\": \"JTNRNB\",\n            \"LastName\": \"LYYSHTQJRE\",\n            \"Age\": \"47\"\n        }\n    ]\n}\n```\n  \n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n#### GetCustomer\nGetCustomer returns Customer by provided id\n\u003ctable\u003e\n\u003ctr\u003e \u003cth\u003e Request \u003c/th\u003e \u003cth\u003e Response \u003c/th\u003e \u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n  \n```json\n{\n    \"CustomerID\": 268\n}\n```\n  \n\u003c/td\u003e\n\u003ctd\u003e\n  \n```json\n{\n    \"Customer\": {\n        \"Id\": \"268\",\n        \"FirstName\": \"MKZPVX\",\n        \"LastName\": \"CBIHNABLQI\",\n        \"Age\": \"54\"\n    }\n}\n```\n  \n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n#### AddCustomer\nAddCustomer adds passed Customer and returns his id. Passed customer \"Id\" field is ignored\n\u003ctable\u003e\n\u003ctr\u003e \u003cth\u003e Request \u003c/th\u003e \u003cth\u003e Response \u003c/th\u003e \u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n  \n```json\n{\n    \"Customer\": {\n        \"Age\": 30,\n        \"FirstName\": \"John\",\n        \"LastName\": \"Doe\"\n    }\n}\n```\n  \n\u003c/td\u003e\n\u003ctd\u003e\n  \n```json\n{\n    \"CustomerID\": \"20003\"\n}\n```\n  \n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n#### DeleteCustomer\nDeleteCustomer deletes Customer by provided id. Returns empty response if no errors were met\n\u003ctable\u003e\n\u003ctr\u003e \u003cth\u003e Request \u003c/th\u003e \u003cth\u003e Response \u003c/th\u003e \u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n  \n```json\n{\n    \"CustomerID\": 16\n}\n```\n  \n\u003c/td\u003e\n\u003ctd\u003e\n  \n```json\n{}\n```\n  \n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n### Products\n#### GetProducts\nGetProducts returns list of all Products limited by provided limit\n\u003ctable\u003e\n\u003ctr\u003e \u003cth\u003e Request \u003c/th\u003e \u003cth\u003e Response \u003c/th\u003e \u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n  \n```json\n{\n    \"Limit\": 2\n}\n```\n  \n\u003c/td\u003e\n\u003ctd\u003e\n  \n```json\n{\n    \"ProductList\": [\n        {\n            \"Id\": \"1\",\n            \"Title\": \"ACADEMY ACADEMY\",\n            \"Price\": 25.99,\n            \"Quantity\": \"138\"\n        },\n        {\n            \"Id\": \"2\",\n            \"Title\": \"ACADEMY ACE\",\n            \"Price\": 20.99,\n            \"Quantity\": \"118\"\n        }\n    ]\n}\n```\n  \n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n#### GetProduct\nGetProduct returns Product by provided id\n\u003ctable\u003e\n\u003ctr\u003e \u003cth\u003e Request \u003c/th\u003e \u003cth\u003e Response \u003c/th\u003e \u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n  \n```json\n{\n    \"ProductID\": 17\n}\n```\n  \n\u003c/td\u003e\n\u003ctd\u003e\n  \n```json\n{\n    \"Product\": {\n        \"Id\": \"17\",\n        \"Title\": \"ACADEMY ALONE\",\n        \"Price\": 28.99,\n        \"Quantity\": \"114\"\n    }\n}\n```\n  \n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n#### AddProduct\nAddProduct adds passed Product and returns his id. Passed product \"Id\" field is ignored\n\u003ctable\u003e\n\u003ctr\u003e \u003cth\u003e Request \u003c/th\u003e \u003cth\u003e Response \u003c/th\u003e \u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n  \n```json\n{\n    \"Product\": {\n        \"Price\": 10.55,\n        \"Quantity\": 6,\n        \"Title\": \"Product\"\n    }\n}\n```\n  \n\u003c/td\u003e\n\u003ctd\u003e\n  \n```json\n{\n    \"ProductID\": \"10006\"\n}\n```\n  \n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n#### DeleteProduct\nDeleteProduct deletes Product by provided id. Returns empty response if no errors were met\n\u003ctable\u003e\n\u003ctr\u003e \u003cth\u003e Request \u003c/th\u003e \u003cth\u003e Response \u003c/th\u003e \u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n  \n```json\n{\n    \"ProductID\": 58\n}\n```\n  \n\u003c/td\u003e\n\u003ctd\u003e\n  \n```json\n{}\n```\n  \n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n### Orders\n#### GetOrder\nGetOrder gets order by provided id\n\u003ctable\u003e\n\u003ctr\u003e \u003cth\u003e Request \u003c/th\u003e \u003cth\u003e Response \u003c/th\u003e \u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n  \n```json\n{\n    \"OrderID\": 54\n}\n```\n  \n\u003c/td\u003e\n\u003ctd\u003e\n  \n```json\n{\n    \"Order\": {\n        \"Id\": \"54\",\n        \"Date\": {\n            \"seconds\": \"1074124800\"\n        },\n        \"NetAmount\": 311.01,\n        \"Tax\": 25.66,\n        \"TotalAmount\": 336.67,\n        \"ProductList\": [\n            {\n                \"Id\": \"5787\",\n                \"Title\": \"AGENT SHINING\",\n                \"Price\": 9.99,\n                \"Quantity\": \"3\"\n            }\n        ]\n    }\n}\n```\n  \n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n#### GetCustomerOrders\nGetCustomerOrders returns customer orders by provided customer id\n\u003ctable\u003e\n\u003ctr\u003e \u003cth\u003e Request \u003c/th\u003e \u003cth\u003e Response \u003c/th\u003e \u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n  \n```json\n{\n    \"CustomerID\": 359\n}\n```\n  \n\u003c/td\u003e\n\u003ctd\u003e\n  \n```json\n{\n    \"OrderList\": [\n        {\n            \"Id\": \"7453\",\n            \"Date\": {\n                \"seconds\": \"1091836800\"\n            },\n            \"NetAmount\": 124.11,\n            \"Tax\": 10.24,\n            \"TotalAmount\": 134.35,\n            \"ProductList\": [\n                {\n                    \"Id\": \"7114\",\n                    \"Title\": \"AIRPORT CAMELOT\",\n                    \"Price\": 9.99,\n                    \"Quantity\": \"3\"\n                }\n            ]\n        }\n    ]\n}\n```\n  \n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n#### AddOrder\nAddOrder adds order for passed customer id with provided products and returns created order id.  \n\"Title\" and \"Price\" fields in passed ProductList are ignored\n\u003ctable\u003e\n\u003ctr\u003e \u003cth\u003e Request \u003c/th\u003e \u003cth\u003e Response \u003c/th\u003e \u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n  \n```json\n{\n    \"CustomerID\": 36,\n    \"ProductList\": [\n        {\n            \"Id\": 34,\n            \"Quantity\": 2\n        },\n         {\n            \"Id\": 92,\n            \"Quantity\": 10\n        }\n    ]\n}\n```\n  \n\u003c/td\u003e\n\u003ctd\u003e\n  \n```json\n{\n    \"OrderID\": \"12010\"\n}\n```\n  \n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n#### DeleteOrder\nDeleteOrder deletes order with provided order id. Returns empty response if no errors were met\n\u003ctable\u003e\n\u003ctr\u003e \u003cth\u003e Request \u003c/th\u003e \u003cth\u003e Response \u003c/th\u003e \u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n  \n```json\n{\n    \"OrderID\": 14\n}\n```\n  \n\u003c/td\u003e\n\u003ctd\u003e\n  \n```json\n{}\n```\n  \n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexzh7%2Fsample-service","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falexzh7%2Fsample-service","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexzh7%2Fsample-service/lists"}