Skip to content

Commit ea0241e

Browse files
Merge pull request #1 from wagnerdevocelot/fitness-functions
Add Fitness Functions
2 parents aabfe74 + 8087217 commit ea0241e

12 files changed

Lines changed: 400 additions & 35 deletions

File tree

.github/workflows/ci-cd.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ jobs:
2929

3030
- name: Run tests
3131
run: lein test
32+
33+
- name: Run architectural fitness checks
34+
run: ./bin/check-architecture.sh
3235

3336
- name: Build uberjar
3437
run: lein uberjar

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Walue - Portfolio Evaluation Service
22

3+
[![CI/CD Pipeline](https://github.com/yourusername/walue/actions/workflows/ci-cd.yml/badge.svg)](https://github.com/yourusername/walue/actions/workflows/ci-cd.yml)
4+
[![Architectural Fitness](https://img.shields.io/badge/Architectural%20Fitness-Checked-brightgreen.svg)](https://github.com/yourusername/walue/blob/main/src/walue/infra/fitness.clj)
5+
36
A Clojure web service that implements a portfolio evaluation system using Domain-Driven Design (DDD) and Hexagonal Architecture.
47

58
## Overview
@@ -42,6 +45,33 @@ The project follows Hexagonal Architecture (also known as Ports and Adapters) an
4245
- **Adapters**: Implement the interfaces defined by the ports, connecting the domain to external systems (HTTP, databases, etc.).
4346
- **Infrastructure**: Provides technical capabilities like logging and metrics.
4447

48+
### Architectural Fitness Functions
49+
50+
This project includes automated architectural fitness functions that enforce the following rules:
51+
52+
1. **Layer Dependency Rules**:
53+
- Domain can only depend on Clojure stdlib
54+
- Port can only depend on Domain
55+
- Adapter can depend on Domain and Port
56+
- Infrastructure should be self-contained
57+
- Core can depend on all layers
58+
59+
2. **Domain Purity**: Ensures domain code has no external dependencies beyond Clojure stdlib
60+
61+
3. **Circular Dependency Prevention**: Detects and prevents circular dependencies between components
62+
63+
4. **Interface Isolation**: Ensures all ports define protocols (interfaces)
64+
65+
5. **Adapter Implementation**: Verifies that adapters implement at least one port
66+
67+
These checks run automatically in the CI/CD pipeline and locally via:
68+
69+
```bash
70+
./bin/check-architecture.sh
71+
```
72+
73+
The fitness functions prevent architectural degradation over time as new changes are introduced.
74+
4575
## API
4676

4777
### Evaluate Portfolio

bin/check-architecture.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/bash
2+
set -e
3+
4+
echo "Running architectural fitness checks..."
5+
lein run -m walue.infra.fitness

project.clj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
:description "Portfolio evaluation service using Domain-Driven Design and Hexagonal Architecture"
33
:dependencies [[org.clojure/clojure "1.11.1"]
44
[org.clojure/data.json "2.4.0"]
5+
[org.clojure/tools.namespace "1.4.4"]
56
[ring/ring-core "1.9.5"]
67
[ring/ring-jetty-adapter "1.9.5"]
78
[ring/ring-json "0.5.1"]]

src/walue/adapter/http_adapter.clj

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,37 @@
22
(:require [clojure.data.json :as json]
33
[ring.middleware.params :refer [wrap-params]]
44
[ring.middleware.json :refer [wrap-json-body wrap-json-response]]
5-
[walue.infra.logging :as logging]
6-
[walue.port.evaluation-port :as port]))
5+
[walue.port.evaluation-port :as evaluation-port]
6+
[walue.port.logging-port :as logging-port]))
77

8-
(defn- handle-evaluate-portfolio [request evaluation-service]
8+
(defn- handle-evaluate-portfolio [request evaluation-service logging-service]
99
(try
1010
(let [body (:body request)
1111
portfolio (or (get body "portfolio") (get body :portfolio))
1212
criteria (or (get body "criterios") (get body :criterios))]
1313
(if (and portfolio criteria)
14-
(let [result (port/evaluate-portfolio evaluation-service portfolio criteria)]
14+
(let [result (evaluation-port/evaluate-portfolio evaluation-service portfolio criteria)]
1515
{:status 200
1616
:body result})
1717
{:status 400
1818
:body {:error "Invalid request. Portfolio and criteria are required."}}))
1919
(catch Exception e
20-
(logging/error "Error evaluating portfolio:" (.getMessage e))
20+
(logging-port/log-error logging-service (str "Error evaluating portfolio: " (.getMessage e)))
2121
{:status 500
2222
:body {:error "An error occurred while processing your request."}})))
2323

2424
(defn- handle-health-check [_]
2525
{:status 200
2626
:body {:status "UP"}})
2727

28-
(defn make-handler [evaluation-service]
28+
(defn make-handler [evaluation-service logging-service]
2929
(fn [request]
3030
(let [uri (:uri request)
3131
method (:request-method request)]
32-
(logging/info "Request received:" method uri)
32+
(logging-port/log-info logging-service (str "Request received: " method " " uri))
3333
(cond
3434
(and (= uri "/api/evaluate") (= method :post))
35-
(handle-evaluate-portfolio request evaluation-service)
35+
(handle-evaluate-portfolio request evaluation-service logging-service)
3636

3737
(and (= uri "/health") (= method :get))
3838
(handle-health-check request)
@@ -41,27 +41,28 @@
4141
{:status 404
4242
:body {:error "Not found"}}))))
4343

44-
(defn wrap-logging [handler]
44+
(defn wrap-logging [handler logging-service]
4545
(fn [request]
4646
(let [start (System/currentTimeMillis)
4747
response (handler request)
4848
duration (- (System/currentTimeMillis) start)]
49-
(logging/info "Request completed in" duration "ms with status" (:status response))
49+
(logging-port/log-info logging-service
50+
(str "Request completed in " duration " ms with status " (:status response)))
5051
response)))
5152

52-
(defn wrap-exception [handler]
53+
(defn wrap-exception [handler logging-service]
5354
(fn [request]
5455
(try
5556
(handler request)
5657
(catch Exception e
57-
(logging/error "Unhandled exception:" (.getMessage e))
58+
(logging-port/log-error logging-service (str "Unhandled exception: " (.getMessage e)))
5859
{:status 500
5960
:body {:error "Internal server error"}}))))
6061

61-
(defn create-app [evaluation-service]
62-
(-> (make-handler evaluation-service)
63-
(wrap-logging)
64-
(wrap-exception)
62+
(defn create-app [evaluation-service logging-service]
63+
(-> (make-handler evaluation-service logging-service)
64+
(wrap-logging logging-service)
65+
(wrap-exception logging-service)
6566
(wrap-json-body {:keywords? false})
6667
(wrap-json-response)
6768
(wrap-params)))

src/walue/core.clj

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,30 @@
11
(ns walue.core
22
(:require [ring.adapter.jetty :as jetty]
33
[walue.adapter.http-adapter :as http]
4-
[walue.port.evaluation-port :as port]
4+
[walue.port.evaluation-port :as evaluation-port]
5+
[walue.port.logging-port :as logging-port]
56
[walue.infra.logging :as logging]
67
[walue.infra.metrics :as metrics])
78
(:gen-class))
89

910
(def server-atom (atom nil))
1011

1112
(defn start-server [port]
12-
(let [evaluation-service (port/->EvaluationService)
13-
app (http/create-app evaluation-service)
13+
(let [evaluation-service (evaluation-port/->EvaluationService)
14+
logging-service (logging-port/->LoggingService)
15+
app (http/create-app evaluation-service logging-service)
1416
metrics (metrics/init-metrics)
1517
server (jetty/run-jetty app {:port port :join? false})]
1618
(reset! server-atom server)
17-
(logging/info "Server started on port" port)
19+
(logging-port/log-info logging-service (str "Server started on port " port))
1820
server))
1921

2022
(defn stop-server []
2123
(when-let [server @server-atom]
2224
(.stop server)
2325
(reset! server-atom nil)
24-
(logging/info "Server stopped")))
26+
(let [logging-service (logging-port/->LoggingService)]
27+
(logging-port/log-info logging-service "Server stopped"))))
2528

2629
(defn- get-env-var [name default]
2730
(if-let [value (System/getenv name)]
@@ -33,7 +36,9 @@
3336
(try
3437
(Integer/parseInt port-str)
3538
(catch NumberFormatException _
36-
(logging/warn "Invalid PORT environment variable value:" port-str "- using default 8080")
39+
(let [logging-service (logging-port/->LoggingService)]
40+
(logging-port/log-warn logging-service
41+
(str "Invalid PORT environment variable value: " port-str " - using default 8080")))
3742
8080))))
3843

3944
(defn- get-log-level []
@@ -51,9 +56,10 @@
5156
(catch NumberFormatException _
5257
(get-port)))
5358
(get-port))
54-
log-level (get-log-level)]
59+
log-level (get-log-level)
60+
logging-service (logging-port/->LoggingService)]
5561
(logging/set-log-level! log-level)
56-
(logging/info "Starting server with log level:" log-level)
62+
(logging-port/log-info logging-service (str "Starting server with log level: " log-level))
5763
(start-server port)
5864
(.addShutdownHook (Runtime/getRuntime)
5965
(Thread. ^Runnable stop-server))))

0 commit comments

Comments
 (0)