A layered Spring Boot REST API for managing a veterinary clinic. It supports CRUD operations for Doctors, Customers, Animals, Vaccines, Available Working Days, and Appointments. The project demonstrates DTO-based design, validation, business rules, pagination, and meaningful HTTP status handling.
This API implements common veterinary clinic operations:
- Manage doctors, customers, and animals.
- Record vaccines with protection dates and animal association.
- Manage doctors’ available days.
- Create appointments with LocalDateTime, enforcing availability and conflict rules.
Enforced business rules:
- Vaccine: Prevent inserting the same vaccine type (same name + code) for an animal if protection is still active.
- Appointment:
- Only on the hour.
- Doctor must be available on the selected date (based on AvailableDate).
- Prevent conflicts for the same doctor at the same date-time.
- Unique composite on (doctor_id, appointment_date).
- AvailableDate: Unique composite on (doctor_id, available_date).
- Cascade deletions: Removing a Customer also removes associated Animals and their Vaccines/Appointments.
Layered architecture:
- entities/ — JPA entities and relationships
- dao/ — Spring Data JPA repositories
- business/ — service interfaces and implementations (abstracts/concretes)
- api/ — REST controllers
- dto/ — request/response DTOs
- core/ — shared config, ModelMapper, exception handling, result wrappers
Important classes:
- core/config/modelMapper/ModelMapperConfig.java
- core/config/modelMapper/ModelManagerService.java
- core/utils/GlobalExceptionHandler.java
- core/utils/ResultHelper.java, core/result/*
- Java 17+
- Spring Boot (Web, Data JPA, Validation)
- Lombok
- ModelMapper
- PostgreSQL or MySQL
Prerequisites:
- JDK 17+
- PostgreSQL or MySQL running locally
Configure DB in src/main/resources/application.properties:
PostgreSQL:
spring.datasource.url=jdbc:postgresql://localhost:5432/vetclinic
spring.datasource.username=postgres
spring.datasource.password=your_password
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.format_sql=true
MySQL:
spring.datasource.url=jdbc:mysql://localhost:3306/vetclinic?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=your_password
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.format_sql=true
Build and run:
- Maven: mvn clean package && mvn spring-boot:run
- Base URL: http://localhost:8080
- Customer 1—N Animal
- Animal 1—N Vaccine
- Animal 1—N Appointment
- Doctor 1—N AvailableDate
- Doctor 1—N Appointment
- Constraints:
- AvailableDate unique: (doctor_id, available_date)
- Appointment unique: (doctor_id, appointment_date)
Note: For bidirectional relations, avoid Lombok toString/equals/hashCode cycles.
- Controllers consume Request DTOs and return Response DTOs.
- Common validations: @NotNull, @NotBlank, @Email, @Positive
- Request DTOs include relation IDs (e.g., AnimalSaveRequest includes customerId; VaccineSaveRequest includes animalId; AppointmentSaveRequest includes doctorId and animalId).
- Vaccine duplicate rule:
- If there exists a same-type (name+code) vaccine whose protectionFinishDate > now for that animal, reject with 409 Conflict (ALREADY_EXISTS).
- Appointment:
- appointmentDate must be exact hour (mm:ss:nano = 00:00:000).
- Doctor must be available on that date (exists in AvailableDate for given doctor and date).
- No conflict at the same date-time with the same doctor (unique composite and pre-check).
- Deletions:
- Deleting a Customer cascades to delete associated Animals and their Vaccines/Appointments.
GlobalExceptionHandler returns:
- 400 BAD_REQUEST with validation errors list for MethodArgumentNotValidException.
- 404 NOT_FOUND for missing records.
- 409 CONFLICT for ALREADY_EXISTS and DOCTOR_NOT_AVAILABLE cases.
- 400 BAD_REQUEST for VALIDATE_ERROR.
- 500 INTERNAL_SERVER_ERROR for unexpected exceptions.
Result envelope:
- Result or ResultData containing success, message, code, and data.
- Cursor endpoints accept page and pageSize query params.
- Controllers map Page to Page and wrap in CursorResponse.
-
Customers (/v1/customers)
- POST — 201 Created — Body: CustomerSaveRequest → CustomerResponse
- GET /{id} — 200 OK → CustomerResponse
- GET — 200 OK → CursorResponse (filters: ?name=)
- PUT — 200 OK — Body: CustomerUpdateRequest → CustomerResponse
- DELETE /{id} — 200 OK
- GET /{id}/animals — 200 OK → List
-
Animals (/v1/animals)
- POST — 201 Created — Body: AnimalSaveRequest (customerId) → AnimalResponse
- GET /{id} — 200 OK → AnimalResponse
- GET — 200 OK → CursorResponse (filters: ?name=, ?customerId=)
- PUT — 200 OK — Body: AnimalUpdateRequest (optional customerId) → AnimalResponse
- DELETE /{id} — 200 OK
-
Vaccines (/v1/vaccines)
- POST — 201 Created — Body: VaccineSaveRequest (animalId) → VaccineResponse
- GET /{id} — 200 OK → VaccineResponse
- GET — 200 OK → List (filter: ?animalId=)
- GET /expiring — 200 OK → List (filters: ?start=YYYY-MM-DD&end=YYYY-MM-DD)
- PUT — 200 OK — Body: VaccineUpdateRequest → VaccineResponse
- DELETE /{id} — 200 OK
-
Doctors (/v1/doctors)
- POST — 201 Created — Body: DoctorSaveRequest → DoctorResponse
- GET /{id} — 200 OK → DoctorResponse
- GET — 200 OK → CursorResponse (filters: ?name=)
- PUT — 200 OK — Body: DoctorUpdateRequest → DoctorResponse
- DELETE /{id} — 200 OK
-
Available Dates (/v1/available-dates)
- POST — 201 Created — Body: AvailableDateSaveRequest (doctorId) → AvailableDateResponse
- GET /{id} — 200 OK → AvailableDateResponse
- GET — 200 OK → CursorResponse
- GET — 200 OK → List (filter: ?doctorId=)
- PUT — 200 OK — Body: AvailableDateUpdateRequest → AvailableDateResponse
- DELETE /{id} — 200 OK
-
Appointments (/v1/appointments)
- POST — 201 Created — Body: AppointmentSaveRequest (doctorId, animalId) → AppointmentResponse
- GET /{id} — 200 OK → AppointmentResponse
- GET — 200 OK → CursorResponse
- GET — 200 OK → List (filter: ?doctorId=)
- GET — 200 OK → List (filter: ?animalId=)
- GET /between — 200 OK → List (filters: ?start=YYYY-MM-DDTHH:mm:ss&end=YYYY-MM-DDTHH:mm:ss)
- PUT — 200 OK — Body: AppointmentUpdateRequest → AppointmentResponse
- DELETE /{id} — 200 OK
- POST /v1/appointments
{
"appointmentDate": "2025-12-01T10:00:00",
"doctorId": 100,
"animalId": 10
}
- POST /v1/vaccines
{
"name": "Kuduz",
"code": "RAB-01",
"protectionStartDate": "2025-01-01",
"protectionFinishDate": "2026-01-01",
"animalId": 10
}
- GET /v1/vaccines/expiring?start=2025-12-01&end=2026-01-31
- Export your collection to postman/VetClinicRestAPI.postman_collection
- Organize requests by entity with sample JSONs.
- Provide db/seed.sql with ≥5 rows per table.
- Respect constraints:
- AvailableDate unique (doctor_id, available_date)
- Appointment unique (doctor_id, appointment_date), only on the hour, doctor available
- Vaccine duplicate rule (no same type if protection active)
- Load:
- PostgreSQL: psql -U USER -d DB_NAME -f db/seed.sql
- MySQL: mysql -u USER -p DB_NAME < db/seed.sql
- Constructor injection used across services.
- Pagination via Page/Pageable.
- DTO validation via Jakarta Validation.
Katmanlı Spring Boot REST API: veteriner kliniği yönetimi. Doktor, müşteri, hayvan, aşı, müsait gün ve randevu işlemleri. DTO, validasyon, iş kuralları, sayfalama ve anlamlı HTTP statüleri ile.
Bu API ile:
- Doktor/Müşteri/Hayvan yönetimi,
- Aşı kayıtlarının tarihleriyle yönetimi,
- Doktorların çalıştığı günlerin kaydı,
- Randevuların LocalDateTime ile kurallara uygun kaydı yapılır.
İş kuralları:
- Aşı: Aynı tip (ad+kod) aşı aktif koruyuculuk varken eklenemez.
- Randevu:
- Sadece saat başı.
- Doktorun o gün
AvailableDatekaydı olmalı. - Aynı saat ve doktor için çakışma engellenir.
- (doctor_id, appointment_date) eşsiz.
- Müsait Gün: (doctor_id, available_date) eşsiz.
- Silmeler: Customer silinince bağlı Animals ve onların Vaccine/Appointment kayıtları da silinir.
- entities/ — JPA entity’ler
- dao/ — Spring Data JPA repository’ler
- business/ — servis arayüz ve implementasyonları
- api/ — REST controller’lar
- dto/ — Request/Response DTO’lar
- core/ — ortak ayarlar (ModelMapper, Exception, Result sarmalayıcılar)
Önemli sınıflar:
- core/config/modelMapper/ModelMapperConfig.java
- core/config/modelMapper/ModelManagerService.java
- core/utils/GlobalExceptionHandler.java
- core/utils/ResultHelper.java, core/result/*
- Java 17+
- Spring Boot (Web, Data JPA, Validation)
- Lombok
- ModelMapper
- PostgreSQL veya MySQL
Önkoşullar:
- JDK 17+
- PostgreSQL veya MySQL
application.properties örnekleri (PostgreSQL/MySQL) üst bölümde.
Çalıştırma:
- mvn clean package && mvn spring-boot:run
- Base URL: http://localhost:8080
- Customer 1—N Animal
- Animal 1—N Vaccine, 1—N Appointment
- Doctor 1—N AvailableDate, 1—N Appointment
- Eşsizlik:
- AvailableDate: (doctor_id, available_date)
- Appointment: (doctor_id, appointment_date)
- Controller’lar Request DTO alır, Response DTO döner.
- Validasyon: @NotNull, @NotBlank, @Email, @Positive
- İlişkiler ID’ler ile set edilir (animalId, doctorId, customerId vb.).
- Aşı: Aynı tip aşı, koruyuculuk bitmeden tekrar eklenemez → 409.
- Randevu:
- LocalDateTime.
- Saat başı kuralı.
- Doktorun o gün AvailableDate’i olmalı.
- Aynı saat ve doktor randevu çakışması engellenir → 409.
GlobalExceptionHandler:
- 404 NOT_FOUND
- 409 CONFLICT (ALREADY_EXISTS, DOCTOR_NOT_AVAILABLE)
- 400 BAD_REQUEST (VALIDATE_ERROR)
- 500 INTERNAL_SERVER_ERROR (genel)
Yanıtlar: Result/ResultData (success, message, code, data)
- page ve pageSize query parametreleri.
- Page → Page maplenip CursorResponse ile sarılır.
- Customers:
/v1/customers(CRUD,?name=,{id}/animals) - Animals:
/v1/animals(CRUD,?name=,?customerId=) - Vaccines:
/v1/vaccines(CRUD,?animalId=,/expiring?start=&end=) - Doctors:
/v1/doctors(CRUD,?name=) - AvailableDates:
/v1/available-dates(CRUD,?doctorId=) - Appointments:
/v1/appointments(CRUD,?doctorId=,?animalId=,/between?start=&end=)
- POST /v1/appointments
{
"appointmentDate": "2025-12-01T10:00:00",
"doctorId": 100,
"animalId": 10
}
- POST /v1/vaccines
{
"name": "Kuduz",
"code": "RAB-01",
"protectionStartDate": "2025-01-01",
"protectionFinishDate": "2026-01-01",
"animalId": 10
}
- Yol: postman/VetClinicRestAPI.postman_collection
- Her entity için klasör ve örnek gövdeler.
- Yol: db/seed.sql
- Kurallara uygun (AvailableDate/Appointment eşsizlik, saat başı, Vaccine kuralı).
- Yükleme:
- PostgreSQL: psql -U USER -d DB_NAME -f db/seed.sql
- MySQL: mysql -u USER -p DB_NAME < db/seed.sql
- Constructor Injection
- Pagination: Page/Pageable
- DTO Validation: Jakarta Validation