# Run
make run
# Compile
make build
# Test
make test
#Benchmark
make benchThis project implements Hexagonal Architecture, which provides a clean separation of concerns, aggresively decouples the application and makes it more testable and maintainable without excesive amounts of boilerplate code.
Three main layers:
- Domain Layer (
internal/domain/) - Business logic - Ports Layer (
internal/ports/) - Contracts - Adapters Layer (
internal/adapters/) - External implementations (infrastructure, domain agnostic implementations, etc)
- Entities (
internal/domain/entities/)Operation: Buy/Sell operationsTax: Calculated tax amountsMarketSession: Handles trading state, incudingAccumulatedWeightedUnitCostto speed up calculations leveraging basic math haha
- Services (
internal/domain/services/): Business logic implementationOperationService: Core tax calculation logic
- Input Ports (
internal/ports/iport/): Ports receiving external dataOperationService: Contract for tax calculation operations
- API Adapters (
internal/adapters/api/): HTTP layer implementation- Controllers (
internal/adapters/api/controller/): HTTP requests/responses - Server (
internal/adapters/api/server/): HTTP server configuration and routing
- Controllers (
tax/
├── cmd/ # Entry points (just one executable atm)
│ ├── main.go # Executable
│ ├── main_integration_test.go # Integration tests
│ └── main_benchmark_test.go # Benchmark tests
├── internal/ # Bussiness dependant code
│ ├── domain/ # Domain layer
│ │ ├── entities/ # Entities
│ │ │ ├── operation.go # Operation
│ │ │ ├── tax.go # Tax
│ │ │ └── session.go # Market session
│ │ └── services/ # Business services
│ │ └── operation_service.go # Tax calculation
│ ├── ports/ # Ports
│ │ └── iport/ # Input ports
│ │ └── operation_service.go # Operation service interface
│ └── adapters/ # Adapters
│ └── api/ # API adapters
│ ├── controller/ # REST HTTP controllers
│ │ ├── controller.go # Base controller interface
│ │ ├── health_controller.go # Health check endpoint
│ │ └── operation_controller.go # Tax calculation endpoint
│ └── server/ # HTTP server
│ └── server.go # Server configuration and routing
├── go.mod # Dependencies
├── makefile # Commands
└── README.md # Documentation
- Separation of Concerns: Business logic isolated from HTTP concerns
- Testability: Each layer can be tested independently
- Flexibility: Easy to swap implementations
- Maintainability: Clear boundaries make code easier to understand and modify
The /tax endpoint implements the required tax calculation system featuring this flow:
flowchart TD
A[HTTP POST /tax] --> B[OperationController.Tax]
B --> C{JSON Decode Success?}
C -->|No| D[Return 400 Bad Request]
C -->|Yes| E[OperationService.GetTaxes]
E --> F[Initialize MarketSession]
F --> G[Process Each Operation]
G --> H{Operation Type?}
H -->|Buy| I[Session.Buy]
I --> J[Add Tax: 0]
J --> K{More Operations?}
H -->|Sell| L[Session.Sell]
L --> M{Profit > 0?}
M -->|No| N[Add Tax: 0]
M -->|Yes| O{Total > Taxable Amount?}
O -->|No| P[Add Tax: 0]
O -->|Yes| Q{Profit <= Accumulated Losses?}
Q -->|Yes| R[Deduct Losses]
Q -->|No| S[Calculate Net Profit]
R --> T[Add Tax: 0]
S --> U[Calculate Tax: profit * taxRate]
T --> K
U --> K
N --> K
P --> K
K -->|Yes| G
K -->|No| V[Return Tax Array]
V --> W[HTTP 200 OK]
D --> X[HTTP Response]
W --> X
- Weighted Average Cost Method: Uses weighted average for calculating profit/loss on sales
- Losses: Accumulated losses are deducted from future profits
- Tax Threshold: Only operations above a certain amount are taxable
- Stateful Session: Maintains state across the same operation batch to save on calculation costs
- Buy Operations: Always result in 0 tax
- Sell Operations: Tax calculation based on:
- Profit calculation using weighted average cost
- Loss carryforward from previous operations
- Taxable amount threshold of 20,000 (configurable)
- Tax rate of 20% (configurable)
- Session Management: Maintains running totals for:
- Accumulated weighted unit cost
- Number of shares bought
- Accumulated losses for tax deduction
- Invalid JSON: Returns 400 Bad Request
- Insufficient Shares: Returns error when trying to sell more than bought
- Service Errors: Returns 500 Internal Server Error (for example the above error is converted to 500)
First iteration
Simple server implementation with health controller and tax controller, no business logic yet
Second iteration
Operation service with dumb business logic approach and simple integration test simulating a request
Third iteration
Refactored operation_service, entities and tests with a focus based on cleanlyness and expresiveness of the code.
Fourth iteration
Added various tests including an integration test covering all edge cases, achieved full correctness of the implementation.
Was a very entertaining challenge overall, I think I achieved an acceptable balance between maintainability and simplicity for the challenge sake. If you reached this far down thank you for taking the time to read, hope you have a nice week! And please leave your feedback so I can keep improving!
This was a Conekta backend technical challenge