{"id":29353714,"url":"https://github.com/paulshpilsher/internal-transfers","last_synced_at":"2026-05-05T04:39:28.679Z","repository":{"id":303683801,"uuid":"1016149330","full_name":"PaulShpilsher/internal-transfers","owner":"PaulShpilsher","description":"POC Go microservice for internal account management and money transfers.  REST API, PostgreSQL, Iris web framework, and robust decimal handling for financial safety.  Includes Docker support, unit tests, and clean layered architecture.","archived":false,"fork":false,"pushed_at":"2025-07-08T21:36:45.000Z","size":12198,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-07-08T22:24:49.046Z","etag":null,"topics":["docker","docker-compose","go","golang","iris","postgres","transactions","unit-testing"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/PaulShpilsher.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-07-08T15:02:14.000Z","updated_at":"2025-07-08T21:36:48.000Z","dependencies_parsed_at":"2025-07-08T22:35:14.044Z","dependency_job_id":null,"html_url":"https://github.com/PaulShpilsher/internal-transfers","commit_stats":null,"previous_names":["paulshpilsher/internal-transfers"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/PaulShpilsher/internal-transfers","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PaulShpilsher%2Finternal-transfers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PaulShpilsher%2Finternal-transfers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PaulShpilsher%2Finternal-transfers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PaulShpilsher%2Finternal-transfers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/PaulShpilsher","download_url":"https://codeload.github.com/PaulShpilsher/internal-transfers/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PaulShpilsher%2Finternal-transfers/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264379216,"owners_count":23598824,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["docker","docker-compose","go","golang","iris","postgres","transactions","unit-testing"],"created_at":"2025-07-09T02:09:00.933Z","updated_at":"2026-05-05T04:39:28.639Z","avatar_url":"https://github.com/PaulShpilsher.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Internal Transfers Service\n\nA Go-based microservice for managing accounts and internal money transfers, using PostgreSQL as the backend database and Iris as the web framework.\n\n---\n\n## 1. Prerequisites\n\n- **Go**: v1.24+ (see `go.mod`)\n- **Docker** and **Docker Compose**: for local development and running the database/service containers\n- **Make** (optional): for convenience scripts\n\n---\n\n## 2. Installation, Setup, Running, and Testing\n\n### Clone the repository\n\n```bash\ngit clone git@github.com:PaulShpilsher/internal-transfers.git\ncd internal-transfers\n```\n\n### Download Go dependencies\n\n```bash\ngo mod download\n```\n\n### Environment Configuration\n\nIf missing create a `.env.docker` file in the project root with the following content (edit as needed):\n\n```env\nAPP_ENV=docker\nSERVER_PORT=3000\nPOSTGRES_HOST=db\nPOSTGRES_PORT=5432\nPOSTGRES_USER=user\nPOSTGRES_PASSWORD=password\nPOSTGRES_DB=accounts_data\n\n```\n\n### Build and Run with Docker Compose\n\n```bash\ndocker-compose up --build\n```\n\n- The API will be available at [http://localhost:3000](http://localhost:3000)\n- The Postgres database will be available at port 5432\n\n### Tear Down and Clean Volumes\n\n```bash\ndocker-compose down -v\n```\n\n### Run Tests (locally, not in container)\n\n```bash\ngo test ./...\n```\n\n---\n\n## 3. Assumptions\n\n- All monetary values are handled as strings to avoid floating-point errors, using the `shopspring/decimal` library.\n- All monetary values support a maximum decimal precision of 8 digits, as enforced by the service and database.\n- Only one table (`accounts`) is present; transactions are not persisted, only balances are updated.\n- The API is stateless and does not implement authentication.\n- The service expects the database to be initialized with the correct schema (see below).\n\n---\n\n## 4. API Descriptions \u0026 Example `curl` Usage\n\n### Create Account\n\n- **POST** `/accounts`\n- **Request Body:**\n  ```json\n  {\n    \"account_id\": 1,\n    \"initial_balance\": \"100.00\"\n  }\n  ```\n- **Responses:**\n  - `201 Created`: Account successfully created.\n  - `400 Bad Request`: \n    - Invalid request body (malformed JSON)\n    - Validation error (missing/invalid fields)\n    - Invalid initial balance (not a number)\n    - Account ID not positive, balance negative, or precision too high\n  - `409 Conflict`: Account ID already exists.\n  - `500 Internal Server Error`: Any other error (e.g., database error).\n\n**Example:**\n```bash\ncurl -X POST http://localhost:3000/accounts \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"account_id\":1,\"initial_balance\":\"100.00\"}'\n```\n\n---\n\n### Get Account\n\n- **GET** `/accounts/{id}`\n- **Responses:**\n  - `200 OK`: Account found. \n    - **Response Body:**\n      ```json\n      {\n          \"account_id\": 2,\n          \"balance\": \"100.12\"\n      }\n      ```\n  - `400 Bad Request`: Invalid account ID (not a number).\n  - `404 Not Found`: Account not found.\n  - `500 Internal Server Error`: Any other error (e.g., database error, response write error).\n\n**Example:**\n```bash\ncurl http://localhost:3000/accounts/1\n```\n\n---\n\n### Submit Transaction\n\n- **POST** `/transactions`\n- **Request Body:**\n  ```json\n  {\n    \"source_account_id\": 1,\n    \"destination_account_id\": 2,\n    \"amount\": \"10.00\"\n  }\n  ```\n- **Responses:**\n  - `200 OK`: Transaction successful.\n  - `400 Bad Request`: \n    - Invalid request body (malformed JSON)\n    - Validation error (missing/invalid fields)\n    - Invalid amount (not a number)\n    - Source/destination account ID not positive, same account, amount not positive, or precision too high\n    - Insufficient funds\n  - `404 Not Found`: Source or destination account not found.\n  - `500 Internal Server Error`: Any other error (e.g., database error).\n\n**Example:**\n```bash\ncurl -X POST http://localhost:3000/transactions \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"source_account_id\":1,\"destination_account_id\":2,\"amount\":\"10.00\"}'\n```\n\n---\n\n## 5. Project Architecture \u0026 Methodology\n\n- **Layered Architecture**: The project is organized into API handlers, services (business logic), repositories (data access), and models (domain).\n- **Dependency Injection**: Services and repositories are injected into handlers for testability.\n- **Validation**: Uses `go-playground/validator` for request validation.\n- **Testing**: Includes unit tests and mocks for services and repositories.\n- **Error Handling**: Centralized error handling middleware for API responses.\n- **Configuration**: Loaded from environment variables, with `.env.docker` for local/dev.\n\n**Directory Structure:**\n```\ninternal/\n  api/        # HTTP handlers, DTOs, routing\n  config/     # Configuration loading\n  db/         # Database access and repository interfaces\n  model/      # Domain models and errors\n  services/   # Business logic\n  mocks/      # Generated mocks for testing\ndata/\n  postgres/   # Database schema\ncmd/\n  main.go     # Application entrypoint\n```\n\n---\n\n## 6. Configuration\n\nAll configuration is via environment variables (see `.env.docker`):\n\n- `POSTGRES_HOST`, `POSTGRES_PORT`, `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB`: Database connection\n- `SERVER_PORT`: Port for the API server (default: 3000)\n- `APP_ENV`: Application environment (default: development)\n\n---\n\n## 7. Database\n\n- **Schema**: See `data/postgres/schema.sql`\n  ```sql\n  CREATE TABLE IF NOT EXISTS accounts (\n      account_id BIGINT PRIMARY KEY,\n      balance NUMERIC(20, 8) NOT NULL CHECK (balance \u003e= 0),\n      created_at TIMESTAMP NOT NULL DEFAULT NOW(),\n      updated_at TIMESTAMP NOT NULL DEFAULT NOW()\n  );\n  -- Trigger to automatically update updated_at on row update\n  CREATE OR REPLACE FUNCTION update_updated_at_column()\n  RETURNS TRIGGER AS $$\n  BEGIN\n      NEW.updated_at = NOW();\n      RETURN NEW;\n  END;\n  $$ LANGUAGE plpgsql;\n\n  DROP TRIGGER IF EXISTS set_updated_at ON accounts;\n  CREATE TRIGGER set_updated_at\n  BEFORE UPDATE ON accounts\n  FOR EACH ROW\n  EXECUTE FUNCTION update_updated_at_column();\n  ```\n- **Initialization**: The schema is automatically loaded into the database on first run via Docker Compose volume mount.\n- **Note**: The `updated_at` column is automatically updated via a database trigger whenever a row is updated.\n\n---\n\n## 8. Used Packages\n\n- **github.com/kataras/iris/v12**: Web framework for HTTP server and routing.\n- **github.com/lib/pq**: PostgreSQL driver for Go's `database/sql` package.\n- **github.com/shopspring/decimal**: Arbitrary-precision decimal arithmetic for handling money safely.\n- **github.com/go-playground/validator/v10**: Struct and field validation for incoming API requests.\n- **github.com/joho/godotenv**: Loads environment variables from `.env` files for configuration.\n- **github.com/golang/mock**: Mocking framework for unit tests.\n- **github.com/stretchr/testify**: Assertions and test helpers for Go tests.\n- **github.com/DATA-DOG/go-sqlmock**: SQL driver mock for testing database interactions.\n\n---\n\n## 9. Docker Build Process\n\n- The Docker build uses a **multi-stage build**:\n  1. **Build Stage**: Compiles the Go binary.\n  2. **Test Stage**: Runs all Go tests. The build will fail if any test fails.\n  3. **Final Stage**: Copies only the compiled binary and required data into a fresh Alpine image, resulting in a minimal, production-ready image.\n- This approach ensures that only tested, minimal artifacts are shipped in the final container, reducing attack surface and image size.\n\n---\n\n## 10. Areas of Improvement\n\n- Add authentication and authorization for API endpoints.\n- Implement transaction history and persistence.\n- Add pagination and filtering for account listings.\n- Improve error messages and API documentation (e.g., Swagger/OpenAPI).\n- Add health checks and metrics endpoints.\n- Support for running migrations (e.g., with `golang-migrate`).\n- Add CI/CD pipeline for automated testing and deployment.\n- Enhance test coverage, including integration tests.\n- Refactor database transaction handling and consider moving the funds transfer logic from the service layer to the repository layer for better transactional consistency. However, note that this would move business logic out of the service layer, which is generally not desirable, but possible if stricter transactional guarantees are needed. \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpaulshpilsher%2Finternal-transfers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpaulshpilsher%2Finternal-transfers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpaulshpilsher%2Finternal-transfers/lists"}