Spring Boot ile geliştirilen, temel dijital cüzdan işlemlerini yöneten hafif bir REST API projesidir.
MiniWallet; kullanıcı oluşturma, kullanıcıya otomatik cüzdan açma, cüzdana para ekleme, para harcama, bakiye görüntüleme ve işlem geçmişini listeleme özelliklerini içerir. Proje; Spring Boot'un temel konularını gerçek bir iş kuralı üzerinden öğrenmek amacıyla geliştirilmiştir.
Bu proje eğitim ve portföy amacıyla geliştirilmektedir. Gerçek bir banka veya ödeme kuruluşu uygulaması değildir.
- Projenin amacı
- Özellikler
- Kullanılan teknolojiler
- Mimari
- Proje yapısı
- Veritabanı modeli
- API endpointleri
- İstek ve cevap örnekleri
- İş kuralları
- Hata yönetimi
- Projeyi çalıştırma
- Postman ile test
- Öğrenilen Spring konuları
- Gelecekte eklenebilecekler
Bu projenin amacı yalnızca CRUD işlemleri yapmak değildir. Kullanıcı ve cüzdan verilerini yönetirken aşağıdaki gerçek iş kurallarını da uygulamaktır:
- Her kullanıcı oluşturulduğunda kullanıcıya sıfır bakiyeli bir cüzdan açılır.
- Cüzdana yalnızca sıfırdan büyük tutarda para eklenebilir.
- Cüzdandan yalnızca sıfırdan büyük tutarda para harcanabilir.
- Cüzdan bakiyesinden daha büyük bir tutar harcanamaz.
- Başarılı para ekleme ve harcama işlemleri geçmişe kaydedilir.
- İşlem geçmişi en yeni işlemden en eski işleme doğru listelenir.
- Kullanıcı oluşturma
- Kullanıcı bilgilerini ID ile görüntüleme
- Kullanıcı oluşturulurken otomatik cüzdan açma
- Cüzdan bakiyesini görüntüleme
- Cüzdana para ekleme
- Cüzdandan para harcama
- Yetersiz bakiye kontrolü
- Para ekleme ve harcama geçmişini görüntüleme
- DTO kullanımı
- İstek doğrulama
- Merkezi hata yönetimi
- Veritabanı işlemlerinde transaction yönetimi
Projenin öğrenme amacıyla sade kalması için ilk sürümde aşağıdaki özellikler bulunmamaktadır:
- JWT ve kullanıcı girişi
- Spring Security
- Admin paneli
- Kullanıcılar arası para transferi
- React veya başka bir frontend arayüzü
- Redis ve rate limiting
- E-posta gönderimi
- Kafka ve mikroservis mimarisi
- Karmaşık bankacılık kuralları
| Teknoloji | Kullanım amacı |
|---|---|
| Java 21 | Uygulamanın programlama dili |
| Spring Boot 4.1.0 | Uygulamayı ve Spring bileşenlerini çalıştırmak |
| Spring Web MVC | REST endpointlerini oluşturmak |
| Spring Data JPA | Veritabanı işlemlerini yönetmek |
| Hibernate | Entity sınıflarını MySQL tablolarıyla eşleştirmek |
| Jakarta Validation | Gelen istekleri doğrulamak |
| MySQL 8.4 | Uygulama verilerini saklamak |
| Maven | Bağımlılık ve proje yönetimi |
| Docker Desktop | MySQL'i container içerisinde çalıştırmak |
| MySQL Workbench | Veritabanını görsel olarak incelemek |
| Postman | API endpointlerini test etmek |
| IntelliJ IDEA Ultimate | Projeyi geliştirmek ve çalıştırmak |
Projede katmanlı mimari kullanılmaktadır. Her katmanın tek bir temel sorumluluğu vardır.
flowchart LR
Client["Postman / API istemcisi"] -->|"HTTP isteği ve JSON"| Controller[Controller]
Controller -->|"Request DTO"| Service[Service]
Service --> Repository[Repository]
Repository -->|"JPA / Hibernate"| Database[(MySQL)]
Database --> Repository
Repository --> Service
Service -->|"Response DTO"| Controller
Controller -->|"HTTP cevabı ve JSON"| Client
| Katman | Görevi |
|---|---|
| Controller | HTTP isteğini karşılar, URL ve JSON verilerini alır, Service katmanını çağırır. |
| DTO | API'ye gelen ve API'den dönen verinin şeklini belirler. |
| Service | İş kurallarını uygular ve işlemin hangi sırayla yapılacağını yönetir. |
| Repository | JPA aracılığıyla veritabanı işlemlerini gerçekleştirir. |
| Entity | Java nesneleriyle veritabanı tablolarını eşleştirir. |
| Exception | Beklenen hata durumlarını anlamlı sınıflarla temsil eder. |
| GlobalExceptionHandler | Hataları uygun HTTP durum kodu ve JSON cevabına dönüştürür. |
sequenceDiagram
actor Kullanici as Postman
participant Controller as WalletController
participant Service as WalletService
participant Repository as WalletRepository
participant History as WalletTransactionRepository
participant Database as MySQL
Kullanici->>Controller: POST /api/wallets/1/deposit
Controller->>Service: deposit(walletId, request)
Service->>Repository: findById(walletId)
Repository->>Database: Cüzdanı sorgula
Database-->>Repository: Wallet
Repository-->>Service: Wallet
Service->>Service: Yeni bakiyeyi hesapla
Service->>Repository: save(wallet)
Repository->>Database: Bakiyeyi güncelle
Service->>History: save(DEPOSIT işlemi)
History->>Database: Geçmiş kaydını ekle
Service-->>Controller: WalletResponse
Controller-->>Kullanici: 200 OK ve JSON
@Transactional sayesinde bakiye güncelleme ve işlem geçmişi kaydı tek bir bütün olarak ele alınır. İşlemlerden biri başarısız olursa veritabanında yarım kalmış bir sonuç oluşması engellenir.
src/main/java/com/onurerkoc/miniwallet
├── controller
│ ├── UserController.java
│ └── WalletController.java
├── dto
│ ├── ApiErrorResponse.java
│ ├── CreateUserRequest.java
│ ├── DepositRequest.java
│ ├── ExpenseRequest.java
│ ├── TransactionResponse.java
│ ├── UserResponse.java
│ └── WalletResponse.java
├── entity
│ ├── TransactionType.java
│ ├── User.java
│ ├── Wallet.java
│ └── WalletTransaction.java
├── exception
│ ├── EmailAlreadyExistsException.java
│ ├── GlobalExceptionHandler.java
│ ├── InsufficientBalanceException.java
│ ├── UserNotFoundException.java
│ └── WalletNotFoundException.java
├── repository
│ ├── UserRepository.java
│ ├── WalletRepository.java
│ └── WalletTransactionRepository.java
├── service
│ ├── UserService.java
│ └── WalletService.java
└── MiniwalletApplication.java
erDiagram
APP_USERS ||--|| WALLETS : "user_id ile bağlıdır"
WALLETS ||--o{ WALLET_TRANSACTIONS : "wallet_id ile bağlıdır"
APP_USERS {
BIGINT id PK
VARCHAR first_name
VARCHAR last_name
VARCHAR email UK
}
WALLETS {
BIGINT id PK
DECIMAL balance
BIGINT user_id UK
}
WALLET_TRANSACTIONS {
BIGINT id PK
VARCHAR type
DECIMAL amount
VARCHAR description
DATETIME created_at
BIGINT wallet_id
}
Projenin bu öğrenme sürümünde karmaşık JPA ilişki anotasyonları kullanılmamaktadır.
Walletsınıfında kullanıcı bağlantısıLong userId,WalletTransactionsınıfında cüzdan bağlantısı iseLong walletIdalanıyla tutulmaktadır. Diyagramdaki çizgiler bu mantıksal bağlantıları gösterir.
| Alan | Açıklama |
|---|---|
id |
Kullanıcının otomatik oluşturulan benzersiz kimliği |
first_name |
Kullanıcının adı |
last_name |
Kullanıcının soyadı |
email |
Kullanıcının benzersiz e-posta adresi |
| Alan | Açıklama |
|---|---|
id |
Cüzdanın otomatik oluşturulan benzersiz kimliği |
balance |
Cüzdanın güncel bakiyesi |
user_id |
Cüzdanın ait olduğu kullanıcının ID değeri |
| Alan | Açıklama |
|---|---|
id |
İşlem kaydının benzersiz kimliği |
type |
İşlem türü: DEPOSIT veya EXPENSE |
amount |
İşlem tutarı |
description |
İşlem açıklaması |
created_at |
İşlemin gerçekleştiği tarih ve saat |
wallet_id |
İşlemin ait olduğu cüzdanın ID değeri |
Temel adres:
http://localhost:8080
| Metot | Endpoint | Açıklama | Başarılı durum |
|---|---|---|---|
POST |
/api/users |
Yeni kullanıcı oluşturur ve otomatik cüzdan açar. | 201 Created |
GET |
/api/users/{userId} |
Kullanıcıyı ID ile getirir. | 200 OK |
GET |
/api/wallets/{walletId} |
Cüzdanı ve güncel bakiyeyi getirir. | 200 OK |
POST |
/api/wallets/{walletId}/deposit |
Cüzdana para ekler ve işlem kaydı oluşturur. | 200 OK |
POST |
/api/wallets/{walletId}/expense |
Cüzdandan para harcar ve işlem kaydı oluşturur. | 200 OK |
GET |
/api/wallets/{walletId}/transactions |
Cüzdanın işlem geçmişini en yeniden eskiye getirir. | 200 OK |
POST /api/users
Content-Type: application/jsonİstek:
{
"firstName": "Onur",
"lastName": "Erkoç",
"email": "onur@example.com"
}201 Created cevabı:
{
"id": 1,
"firstName": "Onur",
"lastName": "Erkoç",
"email": "onur@example.com"
}Kullanıcı oluşturulduğunda başlangıç bakiyesi 0.00 olan bir cüzdan da otomatik olarak oluşturulur.
GET /api/users/1200 OK cevabı:
{
"id": 1,
"firstName": "Onur",
"lastName": "Erkoç",
"email": "onur@example.com"
}GET /api/wallets/1200 OK cevabı:
{
"id": 1,
"balance": 750.00,
"userId": 1
}POST /api/wallets/1/deposit
Content-Type: application/jsonİstek:
{
"amount": 1000,
"description": "Aylık harçlık"
}200 OK cevabı:
{
"id": 1,
"balance": 1000.00,
"userId": 1
}POST /api/wallets/1/expense
Content-Type: application/jsonİstek:
{
"amount": 250,
"description": "Market alışverişi"
}200 OK cevabı:
{
"id": 1,
"balance": 750.00,
"userId": 1
}Bu işlemden sonra bakiye hesabı:
1000 TL - 250 TL = 750 TL
GET /api/wallets/1/transactions200 OK cevabı:
[
{
"id": 2,
"type": "EXPENSE",
"amount": 250.00,
"description": "Market alışverişi",
"createdAt": "2026-08-01T11:10:00"
},
{
"id": 1,
"type": "DEPOSIT",
"amount": 1000.00,
"description": "Aylık harçlık",
"createdAt": "2026-08-01T11:09:00"
}
]Para ekleme ve harcama tutarı sıfırdan büyük olmalıdır. Bu kontrol DTO sınıflarındaki validation anotasyonlarıyla gerçekleştirilir.
@NotNull
@Positive
private BigDecimal amount;Harcanmak istenen tutar mevcut bakiyeden büyükse işlem durdurulur.
if (wallet.getBalance().compareTo(expenseAmount) < 0) {
throw new InsufficientBalanceException("Yetersiz bakiye");
}Para değerlerinde hassasiyet kaybı yaşamamak için double yerine BigDecimal kullanılmaktadır.
Bir e-posta adresiyle yalnızca bir kullanıcı oluşturulabilir. Repository içerisindeki aşağıdaki metot Spring Data JPA tarafından metot isminden otomatik sorguya dönüştürülür:
boolean existsByEmail(String email);public enum TransactionType {
DEPOSIT,
EXPENSE
}Enum değerleri veritabanında okunabilir metinler olarak saklanır.
Uygulamada oluşan beklenen hatalar GlobalExceptionHandler tarafından merkezi olarak yakalanır.
| Durum | HTTP kodu | Açıklama |
|---|---|---|
| Başarılı veri getirme veya cüzdan işlemi | 200 |
İstek başarıyla tamamlandı. |
| Kullanıcı oluşturma | 201 |
Yeni kullanıcı başarıyla oluşturuldu. |
| Geçersiz veya eksik istek | 400 |
Validation kurallarından biri sağlanmadı. |
| Kullanıcı veya cüzdan bulunamadı | 404 |
İstenen kaynak veritabanında bulunamadı. |
| E-posta zaten kullanılıyor | 409 |
Aynı e-posta adresiyle başka kullanıcı var. |
| Bakiye yetersiz | 409 |
Harcama tutarı mevcut bakiyeden büyük. |
Örnek hata cevabı:
{
"message": "Yetersiz bakiye"
}- IntelliJ IDEA Ultimate
- JDK 21
- Docker Desktop
- MySQL Workbench
- Postman
- Git
git clone https://github.com/onurerkoc-dev/miniwallet.gitProjeyi IntelliJ IDEA ile açın ve Maven bağımlılıklarının yüklenmesini bekleyin.
Docker Desktop üzerinden:
- Images bölümünü açın.
mysql:8.4image'ını indirin.- Image yanındaki Run düğmesine basın.
- Aşağıdaki ayarları girin.
| Ayar | Değer |
|---|---|
| Container name | miniwallet-mysql |
| Host port | 3307 |
| Container port | 3306 |
Environment variables:
| Değişken | Örnek değer |
|---|---|
MYSQL_ROOT_PASSWORD |
Güçlü bir root parolası |
MYSQL_DATABASE |
miniwallet_db |
MYSQL_USER |
miniwallet_user |
MYSQL_PASSWORD |
Güçlü bir kullanıcı parolası |
Verilerin container silindiğinde kaybolmaması için Docker volume veya bilgisayarda kalıcı bir klasör kullanılabilir. Container içindeki hedef klasör:
/var/lib/mysql
Gerçek parolaları GitHub'a, README dosyasına veya
application.propertiesiçine yazmayın.
| Alan | Değer |
|---|---|
| Connection name | MiniWallet Local |
| Hostname | 127.0.0.1 |
| Port | 3307 |
| Username | miniwallet_user |
| Password | Docker'da belirlediğiniz MYSQL_PASSWORD değeri |
Bağlantıyı doğrulamak için Test Connection düğmesini kullanabilirsiniz.
IntelliJ IDEA içinde:
- Run > Edit Configurations bölümünü açın.
MiniwalletApplicationçalışma ayarını seçin.- Environment variables alanına aşağıdaki değerleri ekleyin:
DB_USERNAME=miniwallet_user;DB_PASSWORD=Docker'da_belirlediğiniz_parola
Uygulama bu değerleri application.properties dosyasında aşağıdaki şekilde okur:
spring.datasource.url=jdbc:mysql://localhost:3307/miniwallet_db
spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}IntelliJ IDEA'da MiniwalletApplication sınıfını çalıştırın. Uygulama varsayılan olarak aşağıdaki adreste açılır:
http://localhost:8080
İsteğe bağlı olarak IntelliJ terminalinde Windows Maven Wrapper ile de çalıştırılabilir:
.\mvnw.cmd spring-boot:runUygulama başladıktan sonra MySQL Workbench üzerinde aşağıdaki sorguları çalıştırabilirsiniz:
USE miniwallet_db;
SHOW TABLES;
SELECT * FROM app_users;
SELECT * FROM wallets;
SELECT *
FROM wallet_transactions
ORDER BY created_at DESC;- Uygulamanın IntelliJ IDEA'da çalıştığını kontrol edin.
- Docker Desktop'ta
miniwallet-mysqlcontainer'ının Running durumunda olduğunu kontrol edin. - Postman'da yeni bir HTTP isteği oluşturun.
POSTisteklerinde Body > raw > JSON seçeneğini kullanın.- Önce kullanıcı oluşturun; ardından oluşan cüzdan ID değeriyle cüzdan endpointlerini test edin.
Önerilen test sırası:
- Geçerli kullanıcı oluşturma —
201 Created - Eksik veya geçersiz kullanıcı oluşturma —
400 Bad Request - Aynı e-postayla tekrar kullanıcı oluşturma —
409 Conflict - Kullanıcıyı ID ile getirme —
200 OK - Olmayan kullanıcıyı getirme —
404 Not Found - Cüzdanı görüntüleme —
200 OK - Cüzdana para ekleme —
200 OK - Cüzdandan para harcama —
200 OK - Bakiyeden fazla harcama —
409 Conflict - İşlem geçmişini görüntüleme —
200 OK
Bu proje aşağıdaki Spring ve backend geliştirme konularını uygulamalı olarak göstermektedir:
@RestController@RequestMapping@PostMappingve@GetMapping@PathVariableve@RequestBody- Entity ve tablo eşleştirme
- Spring Data JPA ve
JpaRepository - Derived query metotları
- Controller, Service ve Repository katmanları
- Request ve Response DTO kullanımı
- Jakarta Validation
- Özel exception sınıfları
@RestControllerAdviceile global exception handling@Transactionalile transaction yönetimiBigDecimalile para hesaplama- Enum kullanımı
- MySQL bağlantısı
- Docker Desktop ile veritabanı çalıştırma
- Postman ile REST API testi
- Branch, commit, push ve pull request iş akışı
Projenin temel sürümü tamamlandıktan ve ilgili konular öğrenildikten sonra aşağıdaki özellikler eklenebilir:
- Swagger / OpenAPI dokümantasyonu
- Unit ve integration testleri
- Docker Compose
- Uygulama için Dockerfile
- JWT ile kimlik doğrulama
- Spring Security
- Kullanıcının yalnızca kendi cüzdanını görmesi
- Kullanıcılar arası para transferi
- Sayfalama ve işlem geçmişi filtreleme
- Production ortamı için migration yönetimi
- Basit bir frontend arayüzü
Proje deposu: github.com/onurerkoc-dev/miniwallet