17 changed files with 818 additions and 407 deletions
@ -0,0 +1,254 @@
|
||||
--- |
||||
Checks: >- |
||||
-*, |
||||
bugprone-assert-side-effect, |
||||
bugprone-bad-signal-to-kill-thread, |
||||
bugprone-bool-pointer-implicit-conversion, |
||||
bugprone-branch-clone, |
||||
bugprone-copy-constructor-init, |
||||
bugprone-dangling-handle, |
||||
bugprone-dynamic-static-initializers, |
||||
bugprone-exception-escape, |
||||
bugprone-forward-declaration-namespace, |
||||
bugprone-forwarding-reference-overload, |
||||
bugprone-inaccurate-erase, |
||||
bugprone-incorrect-roundings, |
||||
bugprone-infinite-loop, |
||||
bugprone-integer-division, |
||||
bugprone-macro-parentheses, |
||||
bugprone-misplaced-operator-in-strlen-in-alloc, |
||||
bugprone-misplaced-pointer-arithmetic-in-alloc, |
||||
bugprone-misplaced-widening-cast, |
||||
bugprone-move-forwarding-reference, |
||||
bugprone-multiple-statement-macro, |
||||
bugprone-not-null-terminated-result, |
||||
bugprone-parent-virtual-call, |
||||
bugprone-posix-return, |
||||
bugprone-signed-char-misuse, |
||||
bugprone-sizeof-container, |
||||
bugprone-sizeof-expression, |
||||
bugprone-spuriously-wake-up-functions, |
||||
bugprone-string-constructor, |
||||
bugprone-string-integer-assignment, |
||||
bugprone-string-literal-with-embedded-nul, |
||||
bugprone-suspicious-enum-usage, |
||||
bugprone-suspicious-include, |
||||
bugprone-suspicious-memset-usage, |
||||
bugprone-suspicious-missing-comma, |
||||
bugprone-suspicious-semicolon, |
||||
bugprone-suspicious-string-compare, |
||||
bugprone-swapped-arguments, |
||||
bugprone-terminating-continue, |
||||
bugprone-throw-keyword-missing, |
||||
bugprone-too-small-loop-variable, |
||||
bugprone-undefined-memory-manipulation, |
||||
bugprone-undelegated-constructor, |
||||
bugprone-unhandled-self-assignment, |
||||
bugprone-unhandled-self-assignment, |
||||
bugprone-unused-raii, |
||||
bugprone-use-after-move, |
||||
|
||||
cert-dcl21-cpp, |
||||
cert-dcl50-cpp, |
||||
cert-dcl58-cpp, |
||||
cert-env33-c, |
||||
cert-err34-c, |
||||
cert-err52-cpp, |
||||
cert-err58-cpp, |
||||
cert-err60-cpp, |
||||
cert-flp30-c, |
||||
cert-mem57-cpp, |
||||
cert-msc50-cpp, |
||||
cert-msc51-cpp, |
||||
cert-oop57-cpp, |
||||
cert-oop58-cpp, |
||||
|
||||
clang-analyzer-core.CallAndMessage, |
||||
clang-analyzer-core.DivideZero, |
||||
|
||||
clang-analyzer-core.*, |
||||
clang-analyzer-cplusplus.*, |
||||
clang-analyzer-deadcode.*, |
||||
clang-analyzer-nullability.*, |
||||
clang-analyzer-optin.*, |
||||
clang-analyzer-valist.*, |
||||
clang-analyzer-security.*, |
||||
|
||||
cppcoreguidelines-avoid-goto, |
||||
cppcoreguidelines-avoid-non-const-global-variables, |
||||
cppcoreguidelines-init-variables, |
||||
cppcoreguidelines-interfaces-global-init, |
||||
cppcoreguidelines-macro-usage, |
||||
cppcoreguidelines-narrowing-conversions, |
||||
cppcoreguidelines-no-malloc, |
||||
cppcoreguidelines-owning-memory, |
||||
cppcoreguidelines-pro-bounds-array-to-pointer-decay, |
||||
cppcoreguidelines-pro-bounds-constant-array-index, |
||||
cppcoreguidelines-pro-bounds-pointer-arithmetic, |
||||
cppcoreguidelines-pro-type-const-cast, |
||||
cppcoreguidelines-pro-type-cstyle-cast, |
||||
cppcoreguidelines-pro-type-member-init, |
||||
cppcoreguidelines-pro-type-reinterpret-cast, |
||||
cppcoreguidelines-pro-type-static-cast-downcast, |
||||
cppcoreguidelines-pro-type-union-access, |
||||
cppcoreguidelines-pro-type-vararg, |
||||
cppcoreguidelines-slicing, |
||||
cppcoreguidelines-special-member-functions, |
||||
|
||||
google-build-namespaces, |
||||
google-default-arguments, |
||||
google-explicit-constructor, |
||||
google-build-using-namespace, |
||||
google-global-names-in-headers, |
||||
google-readability-casting, |
||||
google-runtime-int, |
||||
google-runtime-operator, |
||||
|
||||
hicpp-exception-baseclass, |
||||
hicpp-multiway-paths-covered, |
||||
hicpp-no-assembler, |
||||
hicpp-signed-bitwise, |
||||
llvm-namespace-comment, |
||||
|
||||
misc-definitions-in-headers, |
||||
misc-misplaced-const, |
||||
misc-new-delete-overloads, |
||||
misc-no-recursion, |
||||
misc-non-copyable-objects, |
||||
misc-non-private-member-variables-in-classes, |
||||
misc-redundant-expression, |
||||
misc-static-assert, |
||||
misc-throw-by-value-catch-by-reference, |
||||
misc-unconventional-assign-operator, |
||||
misc-uniqueptr-reset-release, |
||||
misc-unused-parameters, |
||||
misc-unused-using-decls, |
||||
misc-unused-alias-decls, |
||||
|
||||
modernize-avoid-bind, |
||||
modernize-avoid-c-arrays, |
||||
modernize-concat-nested-namespaces, |
||||
modernize-deprecated-headers, |
||||
modernize-deprecated-ios-base-aliases, |
||||
modernize-loop-convert, |
||||
modernize-make-shared, |
||||
modernize-make-unique, |
||||
modernize-raw-string-literal, |
||||
modernize-redundant-void-arg, |
||||
modernize-replace-auto-ptr, |
||||
modernize-replace-disallow-copy-and-assign-macro, |
||||
modernize-replace-random-shuffle, |
||||
modernize-return-braced-init-list, |
||||
modernize-shrink-to-fit, |
||||
modernize-unary-static-assert, |
||||
modernize-use-auto, |
||||
modernize-use-bool-literals, |
||||
modernize-use-default-member-init, |
||||
modernize-use-emplace, |
||||
modernize-use-equals-default, |
||||
modernize-use-equals-delete, |
||||
modernize-use-nodiscard, |
||||
modernize-use-noexcept, |
||||
modernize-use-nullptr, |
||||
modernize-use-override, |
||||
modernize-use-transparent-functors, |
||||
modernize-use-uncaught-exceptions, |
||||
modernize-use-using, |
||||
|
||||
performance-faster-string-find, |
||||
performance-for-range-copy, |
||||
performance-implicit-conversion-in-loop, |
||||
performance-inefficient-algorithm, |
||||
performance-inefficient-string-concatenation, |
||||
performance-inefficient-vector-operation, |
||||
performance-move-const-arg, |
||||
performance-move-constructor-init, |
||||
performance-no-automatic-move, |
||||
performance-noexcept-move-constructor, |
||||
performance-trivially-destructible, |
||||
performance-type-promotion-in-math-fn, |
||||
performance-unnecessary-copy-initialization, |
||||
performance-unnecessary-value-param, |
||||
|
||||
readability-avoid-const-params-in-decls, |
||||
readability-braces-around-statements, |
||||
readability-const-return-type, |
||||
readability-container-size-empty, |
||||
readability-delete-null-pointer, |
||||
readability-deleted-default, |
||||
readability-else-after-return, |
||||
readability-function-size, |
||||
readability-identifier-naming, |
||||
readability-implicit-bool-conversion, |
||||
readability-inconsistent-declaration-parameter-name, |
||||
readability-isolate-declaration, |
||||
readability-magic-numbers, |
||||
readability-make-member-function-const, |
||||
readability-misleading-indentation, |
||||
readability-misplaced-array-index, |
||||
readability-named-parameter, |
||||
readability-non-const-parameter, |
||||
readability-redundant-control-flow, |
||||
readability-redundant-declaration, |
||||
readability-redundant-function-ptr-dereference, |
||||
readability-redundant-member-init, |
||||
readability-redundant-preprocessor, |
||||
readability-redundant-smartptr-get, |
||||
readability-redundant-string-cstr, |
||||
readability-redundant-string-init, |
||||
readability-simplify-boolean-expr, |
||||
readability-simplify-subscript-expr, |
||||
readability-static-accessed-through-instance, |
||||
readability-static-definition-in-anonymous-namespace, |
||||
readability-string-compare, |
||||
readability-uniqueptr-delete-release, |
||||
readability-uppercase-literal-suffix, |
||||
readability-use-anyofallof |
||||
WarningsAsErrors: '' |
||||
HeaderFilterRegex: '.*' |
||||
AnalyzeTemporaryDtors: false |
||||
FormatStyle: none |
||||
CheckOptions: |
||||
- key: cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor |
||||
value: 1 |
||||
- key: modernize-use-nullptr.NullMacros |
||||
value: 'NULL' |
||||
- key: readability-function-size.LineThreshold |
||||
value: 50 |
||||
- key: readability-function-size.StatementThreshold |
||||
value: 800 |
||||
- key: readability-function-size.BranchThreshold |
||||
value: 10 |
||||
- key: readability-function-size.ParameterThreshold |
||||
value: 6 |
||||
- key: readability-function-size.NestingThreshold |
||||
value: 15 |
||||
- key: readability-function-size.VariableThreshold |
||||
value: 10 |
||||
- key: readability-identifier-naming.ClassCase |
||||
value: CamelCase |
||||
- key: readability-identifier-naming.MemberCase |
||||
value: camelBack |
||||
- key: readability-identifier-naming.ClassMemberCase |
||||
value: camelBack |
||||
- key: readability-identifier-naming.ClassMethodCase |
||||
value: camelBack |
||||
- key: readability-identifier-naming.MethodCase |
||||
value: camelBack |
||||
- key: readability-identifier-naming.ConstantCase |
||||
value: UPPER_CASE |
||||
- key: readability-identifier-naming.LocalConstantCase |
||||
value: camelBack |
||||
- key: readability-identifier-naming.NamespaceCase |
||||
value: lower_case |
||||
- key: readability-identifier-naming.ParameterCase |
||||
value: camelBack |
||||
- key: readability-identifier-naming.EnumCase |
||||
value: CamelCase |
||||
- key: readability-identifier-naming.EnumConstantCase |
||||
value: CamelCase |
||||
- key: readability-identifier-naming.FunctionCase |
||||
value: camelBack |
||||
- key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic |
||||
value: 1 |
||||
... |
||||
@ -1,130 +1,44 @@
|
||||
# About this Repository |
||||
# Ovierview |
||||
|
||||
This repository hosts multiple implementations of the same back-end application. The aim is to provide quick, side-by-side comparisons of different technologies (languages, frameworks, libraries) while preserving consistent business logic across all implementations. |
||||
Read top-level `README.md` for more information on this repository. |
||||
|
||||
Following principles such as **SOLID** and maintainable architectural patterns (**Clean, Hexagonal, Onion, or even DDD**) is recommended to clearly showcase the strengths and idioms of each technology. |
||||
# Authentication |
||||
|
||||
Some over-engineering is acceptable to demonstrate architectural features, but please keep implementations readable and avoid excessive complexity (e.g., skip event sourcing or strict CQRS unless intentionally focusing on those patterns for comparison). |
||||
No external service is used. JWT tokens are created and verified using `jwt-cpp` library. |
||||
Default, pre-defined user databse is a simple json file (`app/defaults/users.json`). |
||||
|
||||
--- |
||||
# Build and Run |
||||
|
||||
### Project Idea: AutoStore |
||||
|
||||
A system to store items with expiration dates. When items expire, new ones are automatically ordered by making a POST request to the configured order URL. |
||||
|
||||
#### Business Rules (Domain) |
||||
|
||||
1. **Each item has a name and an expiration date.** |
||||
2. **Expired items are automatically removed from the store.** |
||||
3. **When an item expires, a new item of the same type is automatically ordered.** |
||||
4. **Expired items can be added to the store, triggering immediate ordering.** |
||||
5. **Every item belongs to a user.** |
||||
|
||||
#### Application Requirements |
||||
|
||||
1. **Users can log in to obtain a JWT.** |
||||
2. **Authenticated users manage their personal collection of items via an HTTP API.** |
||||
3. **Each item has an associated "order URL".** |
||||
4. **When an item expires, the system must notify the "order URL" with an HTTP POST request.** |
||||
5. **This call should occur immediately when the item's expiration date is reached, or when an expired item is added.** |
||||
6. **Upon startup, the system must verify expiration dates for all items.** |
||||
7. **Persistent storage must be used (file, database, etc.).** |
||||
|
||||
**Note:** For simplicity, user CRUD is skipped. Integrate with an OP (OpenID Provider) service like Keycloak, Authentic, or Zitadel, or mock authentication with a simple Docker service. Alternatively, simply authenticate a predefined user and return a JWT on login. |
||||
|
||||
|
||||
--- |
||||
|
||||
## Layer Boundaries |
||||
```bash |
||||
cd docker |
||||
docker compose build |
||||
docker compose up |
||||
``` |
||||
|
||||
| Layer | Responsibility | Internal Dependencies | External Dependencies | |
||||
|-------------------|--------------------------------------------------------------- |----------------------|-----------------------| |
||||
| **Domain** | Entities, value objects, domain services (pure business logic) | None | None (language only) | |
||||
| **Application** | Use cases, orchestration, DTOs, infrastructure interfaces | Domain | None or minimal | |
||||
| **Infrastructure**| Implementations (repositories, HTTP, auth), background jobs | Application | Any (framework/lib) | |
||||
| **Presentation** | API controllers, DTOs, auth middleware | Application | UI/web/CLI/others | |
||||
| **Assembly** | Main app, DI, startup logic, job scheduling | Any layer | DI container, config, framework, etc.| |
||||
Note: do not use this for development. See `.devcontainer` directory for development setup. |
||||
|
||||
--- |
||||
For non-container development, see Dockerfile and replicate the steps. Simple `build-and-test.sh` |
||||
script would look like this: |
||||
|
||||
### Possible directory layout (will vary from tech to tech) |
||||
```bash |
||||
#/bin/bash |
||||
|
||||
```plaintext |
||||
AutoStore/ |
||||
├── App |
||||
│ ├── Main |
||||
│ ├── AppConfig |
||||
│ └── ... |
||||
├── Extern |
||||
│ ├── <jwt-lib, http-client, etc.> |
||||
│ └── <...downloaded libraries and git submodules> |
||||
├── Src |
||||
│ ├── Domain/ |
||||
│ │ ├── Entities/ |
||||
│ │ │ ├── User |
||||
│ │ │ └── Item |
||||
│ │ └── Services/ |
||||
│ │ └── ExpirationPolicy |
||||
│ ├── Application/ |
||||
│ │ ├── UseCases/ |
||||
│ │ │ ├── RegisterUser |
||||
│ │ │ ├── LoginUser |
||||
│ │ │ ├── AddItem |
||||
│ │ │ ├── GetItem |
||||
│ │ │ ├── DeleteItem |
||||
│ │ │ └── HandleExpiredItems |
||||
│ │ ├── Interfaces/ |
||||
│ │ │ ├── IUserRepository |
||||
│ │ │ ├── IItemRepository |
||||
│ │ │ ├── IAuthService |
||||
│ │ │ └── ITimeProvider |
||||
│ │ ├── Dto/ |
||||
│ │ └── Services/ |
||||
│ ├── Infrastructure/ |
||||
│ │ ├── Repositories/ |
||||
│ │ │ ├── FileUserRepository |
||||
│ │ │ └── FileItemRepository |
||||
│ │ ├── Adapters/ |
||||
│ │ │ ├── JwtAuthAdapter |
||||
│ │ │ ├── OrderUrlHttpClient |
||||
│ │ │ ├── SystemClockImpl |
||||
│ │ │ └── <... some extern lib adapters> |
||||
│ │ └── Helpers/ |
||||
│ │ └── <... DRY helpers> |
||||
│ └── WebApi/ |
||||
│ ├── Controllers/ |
||||
│ │ ├── StoreController |
||||
│ │ └── UserController |
||||
│ └── Auth/ |
||||
│ └── JwtMiddleware |
||||
└── Tests |
||||
├── Unit/ |
||||
└── Integration/ |
||||
``` |
||||
OUT_DIR=./out |
||||
DEBUG_DIR=$OUT_DIR/build/debug |
||||
|
||||
## Build and Run |
||||
cmake -DCMAKE_BUILD_TYPE=Debug \ |
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=TRUE \ |
||||
-DCMAKE_TOOLCHAIN_FILE=/opt/vcpkg/scripts/buildsystems/vcpkg.cmake \ |
||||
-S /workspace -B $DEBUG_DIR |
||||
|
||||
Ideally, each implementation should include a `<impl>/docker/docker-compose.yml` file so that you can simply run: |
||||
cd "$DEBUG_DIR" |
||||
cmake --build . -- -j8 |
||||
ctest --output-on-failure . |
||||
|
||||
```bash |
||||
docker compose up |
||||
``` |
||||
to build and run the application. |
||||
|
||||
Otherwise, please provide a `<impl>/README.md` file with setup and running instructions. |
||||
|
||||
## API Endpoints |
||||
|
||||
See `openapi.yaml` file for suggested API (test it with Tavern, Postman etc.). |
||||
Here's a summary of example API endpoints: |
||||
# Testing |
||||
|
||||
| Endpoint | Method | Description | |
||||
|-------------------------|--------|--------------------------------------| |
||||
| `/login` | POST | Authenticate user and get JWT token | |
||||
| `/items` | GET | Get user's items | |
||||
| `/items` | POST | Create new item | |
||||
| `/items/{id}` | GET | Get item by ID | |
||||
| `/items/{id}` | PUT | Update item details | |
||||
| `/items/{id}` | DELETE | Delete item | |
||||
Unit tests are added to ctest and executed on docker build. Execute `ctest .` in build dir to run it. |
||||
|
||||
Suggested base URL is `http://localhost:8080/api/v1/`. |
||||
Use top-level testing/tavern scripts to run functional tests. |
||||
|
||||
@ -1,55 +0,0 @@
|
||||
# C++17 AutoStore Implementation Plan |
||||
|
||||
This document outlines the steps to implement the C++17 version of the AutoStore application. Implemented classes should use `nxl::` namespace prefix. |
||||
|
||||
## Phase 1: Project Scaffolding & Build System |
||||
|
||||
- [x] Initialize a CMake project structure. |
||||
- [x] Set up the root `CMakeLists.txt` to manage the `app` and `lib` subdirectories. |
||||
- [x] Create the `lib` directory for the static library. |
||||
- [x] Create the `app` directory for the executable. |
||||
- [x] Configure `vcpkg` for dependency management and integrate it with CMake. |
||||
- [x] Add a dependency for an HTTP library (e.g., `cpp-httplib`) via `vcpkg`. |
||||
- [x] Add a dependency for a testing framework (e.g., `catch2`) via `vcpkg`. |
||||
|
||||
## Phase 2: Library (`lib`) - Dummy Implementation |
||||
|
||||
- [x] Create the directory structure for the library: `lib/src`, `lib/include`. |
||||
- [x] Create `lib/CMakeLists.txt` to build a static library. |
||||
- [x] In `lib/include/autostore`, define the public interface for the `App` to use. |
||||
- [x] Create a dummy `AutoStore` class in `lib/include/autostore/AutoStore.h` and a source file in `lib/src/AutoStore.cpp`. |
||||
- [ ] Define initial classes for core domain and application logic inside the library (e.g., `ItemRepository`, `UserService`, etc.) to establish the architecture. These will be mostly private to the library initially and implemented later. |
||||
- [ ] Ensure the project compiles and links successfully with the dummy implementations. |
||||
|
||||
## Phase 3: Application (`app`) - Dummy Implementation |
||||
|
||||
- [ ] Create the directory structure for the application: `app/src`. |
||||
- [x] Create `app/CMakeLists.txt` to build the executable. |
||||
- [ ] Link the `app` against the `lib` static library. |
||||
- [ ] Implement the main `App` class in `app/src/App.h` and `app/src/App.cpp`. |
||||
- [ ] The `App` class will have a constructor `App(int argc, char** argv)` and an `exec()` method. |
||||
- [ ] Implement signal handling (for `SIGINT`, `SIGTERM`) in the `App` class for graceful shutdown. |
||||
- [x] In `app/src/Main.cpp`, instantiate and run the `App` class. |
||||
- [ ] Ensure the project compiles and links successfully with the dummy implementations. |
||||
|
||||
## Phase 4: Core Logic Implementation |
||||
|
||||
- [ ] Implement the Domain layer in `lib/src/domain`. |
||||
- [ ] Implement the Application layer in `lib/src/application`. |
||||
- [ ] Implement the Infrastructure layer in `lib/src/infrastructure` (e.g., file-based persistence, HTTP client for ordering). |
||||
- [ ] Implement the Presentation layer (HTTP API) using the chosen HTTP library. |
||||
- [ ] Implement the startup logic to check for expired items. |
||||
- [ ] Implement a background mechanism (e.g., a thread) to periodically check for expired items. |
||||
|
||||
## Phase 5: Testing |
||||
|
||||
- [ ] Set up a `tests` directory. |
||||
- [ ] Create `tests/CMakeLists.txt` to build the test runner. |
||||
- [ ] Write unit tests for the Domain layer. |
||||
- [ ] Write unit tests for the Application layer, using mocks for infrastructure interfaces. |
||||
- [ ] Write integration tests for the Infrastructure layer. |
||||
|
||||
## Phase 6: Containerization |
||||
|
||||
- [x] Create a `Dockerfile` to build the C++ application in a container. |
||||
- [x] Create a `docker-compose.yml` file to easily build and run the application. |
||||
@ -0,0 +1,82 @@
|
||||
# AutoStore Architecture Overview |
||||
|
||||
## Layer Boundaries |
||||
|
||||
```mermaid |
||||
graph TB |
||||
subgraph PL[Presentation Layer] |
||||
A[StoreController] |
||||
B[AuthController] |
||||
end |
||||
|
||||
subgraph AL[Application Layer] |
||||
E[AddItem Use Case] |
||||
F[DeleteItem Use Case] |
||||
G[LoginUser Use Case] |
||||
H[GetItem Use Case] |
||||
I[ListItems Use Case] |
||||
J[HandleExpiredItems Use Case] |
||||
K[TaskScheduler] |
||||
L[IItemRepository] |
||||
M[IAuthService] |
||||
N[IOrderService] |
||||
O[ITimeProvider] |
||||
P[IThreadManager] |
||||
end |
||||
|
||||
subgraph DL[Domain Layer] |
||||
Q[Item] |
||||
R[User] |
||||
S[ItemExpirationPolicy] |
||||
end |
||||
|
||||
subgraph IL[Infrastructure Layer] |
||||
C[HttpServer] |
||||
D[HttpJwtMiddleware] |
||||
T[FileItemRepository] |
||||
U[FileJwtAuthService] |
||||
V[HttpOrderService] |
||||
W[SystemTimeProvider] |
||||
X[SystemThreadManager] |
||||
Y[CvBlocker] |
||||
end |
||||
``` |
||||
|
||||
## Component Dependencies |
||||
|
||||
```mermaid |
||||
graph LR |
||||
StoreController --> AddItem |
||||
StoreController --> DeleteItem |
||||
StoreController --> GetItem |
||||
StoreController --> ListItems |
||||
StoreController --> IAuthService |
||||
|
||||
AuthController --> LoginUser |
||||
|
||||
AddItem --> IItemRepository |
||||
AddItem --> ITimeProvider |
||||
AddItem --> IOrderService |
||||
AddItem --> ItemExpirationPolicy |
||||
|
||||
DeleteItem --> IItemRepository |
||||
|
||||
LoginUser --> IAuthService |
||||
|
||||
GetItem --> IItemRepository |
||||
|
||||
ListItems --> IItemRepository |
||||
|
||||
HandleExpiredItems --> IItemRepository |
||||
HandleExpiredItems --> ITimeProvider |
||||
HandleExpiredItems --> IOrderService |
||||
HandleExpiredItems --> ItemExpirationPolicy |
||||
|
||||
TaskScheduler --> ITimeProvider |
||||
TaskScheduler --> IThreadManager |
||||
|
||||
IItemRepository --> Item |
||||
IAuthService --> User |
||||
IOrderService --> Item |
||||
ItemExpirationPolicy --> Item |
||||
ItemExpirationPolicy --> ITimeProvider |
||||
@ -1,25 +0,0 @@
|
||||
# General request sequence with authentication |
||||
|
||||
```mermaid |
||||
sequenceDiagram |
||||
participant Client as HTTP Client |
||||
participant Router as Request Router |
||||
participant Auth as JwtMiddleware |
||||
participant Controller as Controller |
||||
participant UseCase as Use Case |
||||
|
||||
Client->>Router: POST /api/items (with JWT) |
||||
Router->>Auth: Forward request |
||||
|
||||
alt Authentication successful |
||||
Auth->>Auth: Validate JWT |
||||
Auth->>Controller: Forward authenticated request |
||||
Controller->>Controller: Parse request body to DTO |
||||
Controller->>UseCase: execute() |
||||
UseCase-->>Controller: Result (success/error) |
||||
Controller->>Controller: Convert result to HTTP response |
||||
Controller-->>Client: HTTP Response (2xx) |
||||
else Authentication fails |
||||
Auth-->>Client: 401 Unauthorized |
||||
end |
||||
``` |
||||
@ -0,0 +1,229 @@
|
||||
#include "TaskScheduler.h" |
||||
#include <stdexcept> |
||||
|
||||
namespace nxl::autostore::application { |
||||
|
||||
namespace { |
||||
using Clock = std::chrono::system_clock; |
||||
using TimePoint = Clock::time_point; |
||||
using Duration = Clock::duration; |
||||
using Hours = std::chrono::hours; |
||||
using Minutes = std::chrono::minutes; |
||||
using Seconds = std::chrono::seconds; |
||||
using Milliseconds = std::chrono::milliseconds; |
||||
using Days = std::chrono::duration<int, std::ratio<86400>>; |
||||
using RunMode = TaskScheduler::RunMode; |
||||
|
||||
bool isValidTime(int hour, int minute, int second) |
||||
{ |
||||
return (hour >= 0 && hour <= 23) && (minute >= 0 && minute <= 59) |
||||
&& (second >= 0 && second <= 59); |
||||
} |
||||
|
||||
bool areModesMutuallyExclusive(RunMode mode) |
||||
{ |
||||
return (static_cast<int>(mode) & static_cast<int>(RunMode::Forever)) |
||||
&& (static_cast<int>(mode) & static_cast<int>(RunMode::Once)); |
||||
} |
||||
|
||||
TimePoint todayAt(uint8_t hour, uint8_t minute, uint8_t second, |
||||
const ITimeProvider& timeProvider) |
||||
{ |
||||
auto now = timeProvider.now(); |
||||
auto midnight = |
||||
std::chrono::time_point_cast<Duration>(std::chrono::floor<Days>(now)); |
||||
auto offset = Hours{hour} + Minutes{minute} + Seconds{second}; |
||||
return midnight + offset; |
||||
} |
||||
|
||||
bool shouldExecuteOnStart(const TaskScheduler::ScheduledTask& task) |
||||
{ |
||||
return (static_cast<int>(task.mode) & static_cast<int>(RunMode::OnStart)) |
||||
&& !task.executed; |
||||
} |
||||
|
||||
TimePoint calculateNextExecutionTime(const TaskScheduler::ScheduledTask& task, |
||||
const ITimeProvider& timeProvider, |
||||
TimePoint now) |
||||
{ |
||||
auto taskTime = todayAt(task.hour, task.minute, task.second, timeProvider); |
||||
|
||||
if (taskTime <= now) { |
||||
if (static_cast<int>(task.mode) & static_cast<int>(RunMode::Forever)) { |
||||
taskTime += Hours(24); |
||||
} else if ((static_cast<int>(task.mode) & static_cast<int>(RunMode::Once)) |
||||
&& task.executed) { |
||||
return TimePoint{}; |
||||
} |
||||
} |
||||
|
||||
return taskTime; |
||||
} |
||||
|
||||
bool shouldExecuteBasedOnTime(const TaskScheduler::ScheduledTask& task, |
||||
TimePoint now) |
||||
{ |
||||
if (task.nextExecution == TimePoint{}) { |
||||
return false; |
||||
} |
||||
return task.nextExecution <= now; |
||||
} |
||||
|
||||
void executeTask(TaskScheduler::ScheduledTask& task, ILogger& logger, |
||||
const ITimeProvider& timeProvider, TimePoint& nextWakeupTime) |
||||
{ |
||||
try { |
||||
task.function(); |
||||
task.executed = true; |
||||
logger.info("Task executed successfully"); |
||||
|
||||
if (static_cast<int>(task.mode) & static_cast<int>(RunMode::Forever)) { |
||||
auto nextTaskTime = |
||||
todayAt(task.hour, task.minute, task.second, timeProvider); |
||||
nextTaskTime += Hours(24); |
||||
task.nextExecution = nextTaskTime; |
||||
|
||||
if (nextTaskTime < nextWakeupTime) { |
||||
nextWakeupTime = nextTaskTime; |
||||
} |
||||
} |
||||
} catch (const std::exception& e) { |
||||
logger.error("Task execution failed: %s", e.what()); |
||||
} |
||||
} |
||||
|
||||
void processTasks(std::vector<TaskScheduler::ScheduledTask>& tasks, |
||||
ILogger& logger, const ITimeProvider& timeProvider, |
||||
std::atomic<bool>& stopRequested, TimePoint now, |
||||
bool& hasOnStartTask, TimePoint& nextWakeupTime) |
||||
{ |
||||
for (auto& task : tasks) { |
||||
if (stopRequested) { |
||||
break; |
||||
} |
||||
|
||||
bool executeNow = false; |
||||
|
||||
if (shouldExecuteOnStart(task)) { |
||||
executeNow = true; |
||||
hasOnStartTask = true; |
||||
} else if ((static_cast<int>(task.mode) & static_cast<int>(RunMode::Once)) |
||||
|| (static_cast<int>(task.mode) |
||||
& static_cast<int>(RunMode::Forever))) { |
||||
if (task.nextExecution == TimePoint{}) { |
||||
auto taskTime = calculateNextExecutionTime(task, timeProvider, now); |
||||
|
||||
if ((static_cast<int>(task.mode) & static_cast<int>(RunMode::Once)) |
||||
&& taskTime == TimePoint{} && !task.executed) { |
||||
executeNow = true; |
||||
} else if (taskTime != TimePoint{}) { |
||||
task.nextExecution = taskTime; |
||||
} |
||||
} |
||||
|
||||
if (!executeNow && shouldExecuteBasedOnTime(task, now)) { |
||||
executeNow = true; |
||||
} |
||||
|
||||
if (!executeNow && task.nextExecution < nextWakeupTime) { |
||||
nextWakeupTime = task.nextExecution; |
||||
} |
||||
} |
||||
|
||||
if (executeNow) { |
||||
executeTask(task, logger, timeProvider, nextWakeupTime); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void waitForNextTask(IBlocker& blocker, std::atomic<bool>& stopRequested, |
||||
TimePoint now, TimePoint nextWakeupTime) |
||||
{ |
||||
if (!stopRequested && nextWakeupTime > now) { |
||||
auto waitDuration = |
||||
std::chrono::duration_cast<Milliseconds>(nextWakeupTime - now); |
||||
auto maxWait = Minutes(1); |
||||
|
||||
if (waitDuration > maxWait) { |
||||
waitDuration = maxWait; |
||||
} |
||||
|
||||
blocker.blockFor(waitDuration); |
||||
} |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
TaskScheduler::TaskScheduler(ILoggerPtr logger, ITimeProvider& timeProvider, |
||||
IThreadManager& threadManager, |
||||
std::unique_ptr<IBlocker> blocker) |
||||
: logger{std::move(logger)}, timeProvider{timeProvider}, |
||||
threadManager{threadManager}, blocker{std::move(blocker)} |
||||
{} |
||||
|
||||
void TaskScheduler::schedule(TaskFunction task, int hour, int minute, |
||||
int second, RunMode mode) |
||||
{ |
||||
if (!isValidTime(hour, minute, second)) { |
||||
throw std::invalid_argument("Invalid time parameters"); |
||||
} |
||||
|
||||
if (areModesMutuallyExclusive(mode)) { |
||||
throw std::invalid_argument( |
||||
"Forever and Once modes are mutually exclusive"); |
||||
} |
||||
|
||||
std::lock_guard<std::mutex> lock(tasksMutex); |
||||
tasks.emplace_back(std::move(task), hour, minute, second, mode); |
||||
} |
||||
|
||||
void TaskScheduler::start() |
||||
{ |
||||
if (running) { |
||||
return; |
||||
} |
||||
|
||||
running = true; |
||||
stopRequested = false; |
||||
|
||||
threadHandle = threadManager.createThread([this]() { |
||||
logger->info("TaskScheduler thread started"); |
||||
|
||||
while (!stopRequested) { |
||||
auto now = timeProvider.now(); |
||||
bool shouldExecuteOnStart = false; |
||||
auto nextWakeupTime = now + Hours(24); |
||||
|
||||
{ |
||||
std::lock_guard<std::mutex> lock(tasksMutex); |
||||
processTasks(tasks, *logger, timeProvider, stopRequested, now, |
||||
shouldExecuteOnStart, nextWakeupTime); |
||||
} |
||||
|
||||
if (shouldExecuteOnStart) { |
||||
continue; |
||||
} |
||||
|
||||
waitForNextTask(*blocker, stopRequested, now, nextWakeupTime); |
||||
} |
||||
|
||||
running = false; |
||||
logger->info("TaskScheduler thread stopped"); |
||||
}); |
||||
} |
||||
|
||||
void TaskScheduler::stop() |
||||
{ |
||||
if (!running) { |
||||
return; |
||||
} |
||||
|
||||
stopRequested = true; |
||||
blocker->notify(); |
||||
|
||||
if (threadHandle && threadHandle->joinable()) { |
||||
threadHandle->join(); |
||||
} |
||||
} |
||||
|
||||
} // namespace nxl::autostore::application
|
||||
@ -1,138 +0,0 @@
|
||||
#include "TaskScheduler.h" |
||||
#include <sstream> |
||||
#include <iomanip> |
||||
#include <random> |
||||
#include <stdexcept> |
||||
#include <thread> |
||||
|
||||
namespace nxl::autostore::infrastructure { |
||||
|
||||
namespace { |
||||
|
||||
std::chrono::system_clock::time_point |
||||
today(uint8_t hour, uint8_t minute, uint8_t second, |
||||
const application::ITimeProvider& timeProvider) |
||||
{ |
||||
using namespace std::chrono; |
||||
using days = duration<int, std::ratio<86400>>; |
||||
|
||||
auto now = timeProvider.now(); |
||||
auto midnight = time_point_cast<system_clock::duration>(floor<days>(now)); |
||||
auto offset = hours{hour} + minutes{minute} + seconds{second}; |
||||
return midnight + offset; |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
TaskScheduler::TaskScheduler( |
||||
ILoggerPtr logger, std::shared_ptr<application::ITimeProvider> timeProvider, |
||||
std::shared_ptr<application::IThreadManager> threadManager, |
||||
std::shared_ptr<application::IBlocker> blocker) |
||||
: logger{std::move(logger)}, timeProvider{std::move(timeProvider)}, |
||||
threadManager{std::move(threadManager)}, blocker{std::move(blocker)}, |
||||
running(false), stopRequested(false) |
||||
{} |
||||
|
||||
void TaskScheduler::schedule(TaskFunction task, int hour, int minute, |
||||
int second, RunMode mode) |
||||
{ |
||||
if (hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 |
||||
|| second > 59) { |
||||
throw std::invalid_argument("Invalid time parameters"); |
||||
} |
||||
|
||||
if ((mode & RunMode::Forever) && (mode & RunMode::Once)) { |
||||
throw std::invalid_argument( |
||||
"Forever and Once modes are mutually exclusive"); |
||||
} |
||||
|
||||
std::lock_guard<std::mutex> lock(tasksMutex); |
||||
tasks.emplace_back(std::move(task), hour, minute, second, mode); |
||||
} |
||||
|
||||
void TaskScheduler::start() |
||||
{ |
||||
if (running) { |
||||
return; |
||||
} |
||||
|
||||
running = true; |
||||
stopRequested = false; |
||||
|
||||
threadHandle = threadManager->createThread([this]() { |
||||
logger->info("TaskScheduler thread started"); |
||||
|
||||
while (!stopRequested) { |
||||
auto now = timeProvider->now(); |
||||
bool shouldExecuteOnStart = false; |
||||
|
||||
{ |
||||
std::lock_guard<std::mutex> lock(tasksMutex); |
||||
|
||||
for (auto& task : tasks) { |
||||
if (stopRequested) |
||||
break; |
||||
|
||||
bool executeNow = false; |
||||
|
||||
if ((task.mode & RunMode::OnStart) && !task.executed) { |
||||
executeNow = true; |
||||
shouldExecuteOnStart = true; |
||||
} else if (task.mode & RunMode::Once |
||||
|| task.mode & RunMode::Forever) { |
||||
if (!task.executed || (task.mode & RunMode::Forever)) { |
||||
auto taskTime = |
||||
today(task.hour, task.minute, task.second, *timeProvider); |
||||
|
||||
if (taskTime <= now) { |
||||
if (task.mode & RunMode::Forever) { |
||||
taskTime += std::chrono::hours(24); |
||||
} |
||||
executeNow = true; |
||||
} |
||||
|
||||
task.nextExecution = taskTime; |
||||
} |
||||
} |
||||
|
||||
if (executeNow) { |
||||
try { |
||||
task.function(); |
||||
task.executed = true; |
||||
logger->info("Task executed successfully"); |
||||
} catch (const std::exception& e) { |
||||
logger->error("Task execution failed: %s", e.what()); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (shouldExecuteOnStart) { |
||||
continue; |
||||
} |
||||
|
||||
if (!stopRequested) { |
||||
blocker->blockFor(std::chrono::milliseconds(100)); |
||||
} |
||||
} |
||||
|
||||
running = false; |
||||
logger->info("TaskScheduler thread stopped"); |
||||
}); |
||||
} |
||||
|
||||
void TaskScheduler::stop() |
||||
{ |
||||
if (!running) { |
||||
return; |
||||
} |
||||
|
||||
stopRequested = true; |
||||
blocker->notify(); |
||||
|
||||
if (threadHandle && threadHandle->joinable()) { |
||||
threadHandle->join(); |
||||
} |
||||
} |
||||
|
||||
} // namespace nxl::autostore::infrastructure
|
||||
Loading…
Reference in new issue