Docker, Flyway & PostgreSQL Kullanarak Spring Boot App Geliştirmek
Back-End alanında birkaç aydır çalışmaya başladığım Java Spring oldukça eğlenceli gidiyor. Bu yazıyı yazma amacım hem geçtiğim yolları pekiştirmek hem de yeni başlayacaklara yardımcı olmak, o zaman başlayalım.
Ben MacBook M1 üzerinden projemi geliştirdim, o yüzden bahsedeceğimiz tüm ayarlamalar ve kurulumlar özellikle bu cihaza ithafen olacaktır.
Öncelikle Docker’ı kuralım. Docker, uygulamalarınızı hızla derlemenize, test etmenize ve dağıtmanıza imkan tanıyan bir yazılım platformudur. Ardından PgAdmin 4'u da kuralım. PgAdmin PostgreSQL için popüler açık kaynak yönetim ve geliştirme platformudur. Ben PostgreSQL’i brew üzerinden indirdim. Eğer her şeyden önce bilgisayarınızda brew yoksa onu kurmanız gerekli. Brew kurulumundan sonra; PostgreSQL’ı kurmak için terminal üzerinden çalıştıracağınız tüm kodları bu linkte takip edebilirsiniz. Brew kurulumundan sonra yapmanız gereken 2 şey var:
brew update
brew doctor
Aşağıdaki kod bloğunu terminalde çalıştırıyoruz. Ben şahsen versiyon 15'i kurdum.
brew install postgresql@14
Postgres’i kurduğunuzda Homebrew, Terminalinizde okumanız gereken yararlı bilgiler sağlayacaktır. Homebrew ayrıca yararlı bir şekilde varsayılan bir veritabanı kümesi oluşturur. Şunun gibi bir şey görürseniz bunu onaylayabilirsiniz:
brew services start postgresql@14
Birkaç saniye bekleyin, ardından çalıştığını onaylayın:
brew services list
Çalıştığını doğrulamak çok önemlidir çünkü Homebrew, gerçekte çalışmadığı halde başarıyla postgresql@14 başlatıldı diyebilir. Yeşil olarak başlatıldı yazıyorsa, uygulamanızda veritabanını oluşturmak ve kullanmak için Rails komutlarını çalıştırmaya hazır olmanız gerekir.
Postgres’i durdurmak için:
brew services stop postgresql@14
Eğer terminale “ psql ” yazdığınız zaman “ unknown command ” output’u alıyorsanız şunu yapmanız gerekli:
echo 'export PATH="/Applications/Postgres.app/Contents/Versions/latest/bin:$PATH"' >> ~/.zshrc
Böylelikle, Path’i eklemiş oluyoruz. Eğer bunun dışında başka hatalar ile karşılaşırsanız kurulum sırasında, en aşağıda benim yardım aldığım tüm linkleri eklediğim bir liste göreceksiniz, siz de oradan yardım alabilirsiniz.
Şimdi, eğlenceli kısma gelelim artık.

Spring Initializr bizim için gerekli bileşenleri bir araya getirip IDE’mizde açmamızı sağlayan bir websitesi. Aşağıdaki gibi gerekli boşlukları doldurun ve dependecies ekleyin. Validation’u eklemek sizin opsiyonunuza kalmış.

Her şeyi doğru yaptıktan sonra generate edin ve inen dosyayı favori IDE’nizde açın. Ben Intellij IDEA’yı tavsiye ederim. Açtıktan sonra Maven bileşenleri kendi kendine kurulacaktır.
Şimdi main klasörü içerisinde bir docker-compose.yml isim ve uzantılı bir dosya oluşturalım. O dosyanın içerisini aşağıdaki örnekteki gibi doldurabilirsiniz. Bu dosya bizim docker üzerinden postgreSQL’i ayağa kaldırmamızı sağlayacak.

Bu örnekte aslında auth method’u “trust” olarak ayarladıktan sonra bir password’a ihtiyacınız yok silebilirsiniz. Veya host auth method’u direkt kaldırıp bir şifre belirleyebilirsiniz. Bundan sonra ayağa kaldırmak içinde docker-compose.yml’nin olduğu dizine gidin ve şunu çalıştırın:
docker-compose up -d

Terminal’de buna benzer bir şeyler görmeniz lazım. Böylelikle Image’i yükleyip Container’ı oluşturdu. Docker üzerinden kontrol edin ayağa kalkıp kalkmadığını.
Artık Spring Boot uygulamanızın bir parçası olarak Docker PostgreSQL’e erişim sağlamak için application.properties
dosyasına aşağıdaki satırları ekleyebilirsiniz:
spring.datasource.url=jdbc:postgresql://localhost:5432/dbname
spring.datasource.username=dbuser
spring.datasource.password=dbpassword // şifresiz yaptıysanız gerek yok
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
spring.jpa.hibernate.ddl-auto=update
Burada dbname
, dbuser
ve dbpassword
değerlerini, docker-compose.yml
dosyasında belirttiğiniz değerlerle aynı olacak şekilde ayarlamalısınız.
Şimdi uygulamanız Docker üzerinde PostgreSQL ile iletişim kurmaya hazır. Bu konfigürasyonlar, uygulamanızın PostgreSQL veritabanıyla iletişim kurmasını ve veri alışverişinde bulunmasını sağlayacaktır. Uygulamanızın geri kalanını geliştirebilir ve bu ayarlamaların üzerine inşa edebilirsiniz.

Docker Hakkında Biraz Daha Info (Havada Kalmasın)
Docker, yazılım uygulamalarınızı bir “konteyner” adı verilen hafif, taşınabilir ve ölçeklenebilir bir birim içerisinde paketlemek ve çalıştırmak için kullanılan bir platformdur. Temel olarak, Docker, uygulamanızı çalıştırmak için gerekli olan tüm kod, kütüphane dosyaları, ortam değişkenleri ve bağımlılıkları bir araya getirerek bir konteyner oluşturur. Yani abi sen bir uygulama yaptın arkadaşın pull etti diyor ki bende çalışmıyor, sen de diyorsun ki “aa benim local’imde çalışıyordu??!” işte docker tam olarak da bu durumların yaşanmasını engelliyor.
İşte Docker’ın bazı temel avantajları:
- Taşınabilirlik: Docker konteynerleri, herhangi bir ortamda (lokal bilgisayarlar, bulut hizmetleri, veri merkezleri vb.) çalıştırılabilir. Konteynerler, her yerde aynı şekilde davranır ve taşınabilirliklerini korurlar.
- Hafiflik ve Hız: Konteynerler, sanal makinelerden daha hafif ve daha hızlıdır. Her bir konteyner, işletim sistemi çekirdeği ve temel sistem dosyalarını paylaşırken, uygulama ve bağımlılıkları yalnızca kendine ait olarak çalıştırır.
- İzolasyon: Konteynerler, birbirlerinden ve ana sistemden izole edilmiş ortamlarda çalışır. Bu, uygulamalar arasında çakışma olasılığını azaltır ve güvenliği artırır.
- Ölçeklenebilirlik: Docker, uygulamalarınızı kolayca ölçeklendirebilmenizi sağlar. İhtiyaca göre yeni konteynerler oluşturarak veya var olanları çoğaltarak uygulama performansını artırabilirsiniz. (Buraya ek parantez burası aslında çok önemli , ne kadar gereksiz zımbırtı dependency eklerseniz uygulamanızın başlatılmasını o kadar geciktireceksiniz, kaynakları iyi kullanmanız lazım.)
Docker, geliştirme sürecini kolaylaştırır çünkü tüm ekosistemi standartlaştırır ve ortamın klonlanabilir olduğu anlamına gelir. Bu sayede bir geliştirici, kendi cihazında sorunsuz bir şekilde çalışan bir uygulamayı, başka bir geliştirici veya üretim ortamına hızlıca taşıyabilir.
Özetlemek gerekirse, Docker, uygulamaların hızlı dağıtımı, çalıştırılması ve yönetilmesi için güçlü bir araçtır. Özellikle mikro-servisler mimarisi veya sürekli entegrasyon ve dağıtım (CI/CD) gibi modern yazılım geliştirme uygulamalarında sıkça kullanılır.

Devam Edelim..
Şimdi PgAdmin 4'e gidelim. Veya opsiyonel olarak Intellij Idea’nın database plug-in’den yapabilirsiniz.
Add new server diyerek oluşturun DB Server’ınızı, eğer sizden host name isterse “localhost” olarak doldurun. Burayı çok fazla detaylı anlatmayacağım zira bu yazıyı okuyorsanız database kavramlarına hakim olduğunuzu düşünüyorum. Kendinize dummy data’lar oluşturabilirsiniz data’yı check etmek için.

Şimdi IDE’mize dönelim ve bir Spring projesi yazalım. (yaşasın kod yazıcaz!)
But First… Anotasyonlar
Spring Boot, geliştiricilere uygulama geliştirmeyi kolaylaştırmak için bir dizi anotasyon (annotation) sunar. Anotasyonlar, belirli işlevleri gerçekleştirmek veya yapılandırmak için Java kodunda kullanılan etiketlerdir. Kısacası Spring’i Spring yapan şey anotasyonlardır. Spring Boot’un sunduğu bazı önemli anotasyonlardan bazıları şunlardır:
- @SpringBootApplication: Bu anotasyon, Spring Boot uygulamalarının ana sınıfı üzerinde kullanılır. Bu anotasyon,
@Configuration
,@EnableAutoConfiguration
, ve@ComponentScan
anotasyonlarını bir arada kullanır. Uygulamanın yapılandırılmasını ve otomatik yapılandırmayı etkinleştirir. - @RestController: Bu anotasyon, bir sınıfın bir RESTful web servisi olduğunu belirtmek için kullanılır. Bu anotasyon, sınıfın metotlarının HTTP istekleriyle eşleştirilmesini sağlar ve JSON veya XML gibi veri türlerini HTTP yanıtlarında döndürmeyi kolaylaştırır.
- @RequestMapping: Bu anotasyon, bir HTTP isteğinin belirli bir URL yoluna eşlenmesini sağlar. Metotlar üzerinde kullanılarak, belirli bir URL için bir işlevin hangi HTTP istekleriyle eşleşeceğini belirler.
- @Autowired: Bu anotasyon, bağımlılık enjeksiyonunu gerçekleştirmek için kullanılır. Spring, bu anotasyonu kullanarak ilgili sınıflar arasındaki bağlantıyı otomatik olarak kurar. Constructor’ın üzerine yapıştır gitsin.
- @Service, @Repository, @Component: Bu anotasyonlar, sınıfların belirli işlevlere veya rolleri olan bileşenler olduğunu belirtmek için kullanılır. Örneğin,
@Service
servis sınıflarını işaretlerken,@Repository
veritabanı işlemlerini gerçekleştiren sınıfları ifade eder. - @GetMapping, @PostMapping, @PutMapping, @DeleteMapping: Bu anotasyonlar, sırasıyla GET, POST, PUT ve DELETE HTTP metotlarına karşılık gelen istekleri işlemek için kullanılır. Bu anotasyonlar, belirli URL’ler üzerinde çalışan metodları belirtmek için kullanılır.
- @PathVariable: Bu anotasyon, RESTful servislerde URL yolu içindeki değişkenleri almak için kullanılır. Örneğin,
@RequestMapping("/users/{id}")
şeklinde bir URL yolunda{id}
parametresini almak için@PathVariable
kullanılabilir. - @Configuration: Bu anotasyon, bir sınıfın yapılandırma sınıfı olduğunu belirtmek için kullanılır. Bu sınıflar, Spring uygulamasının yapılandırmasını tanımlamak için kullanılır.
Bu anotasyonlar, Spring Boot’un birçok farklı özelliği ve yeteneğini etkinleştirmeye yardımcı olur ve geliştiricilere yazılım geliştirme sürecini daha hızlı ve verimli hale getirir.

Tamam şimdi valla kod yazacağız artık;
Entity Class’ı
Entity sınıfları, genellikle veritabanı tablolarını temsil etmek için kullanılan ve bir Java sınıfı olarak yazılan sınıflardır. Bu sınıflar genellikle Spring Boot gibi çerçevelerle entegre edilen ve JPA (Java Persistence API) ile ilişkilendirilen projelerde kullanılır. Bu sınıflar, veritabanı tablolarının her bir satırına karşılık gelir ve veritabanında depolanan verilerin modele dönüştürülmesini sağlar.
Bir Entity sınıfının özellikleri şunlar olabilir:
@Entity Anotasyonu: Bu anotasyon, sınıfın bir JPA Entity’si olduğunu belirtir. Bu sınıflar, veritabanında depolanabilir ve JPA tarafından yönetilen nesnelerdir.
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// Getter ve Setter metotları
}
Yukarıdaki örnek, bir User entity sınıfını temsil etmektedir. @Entity
anotasyonu, bu sınıfın bir JPA Entity'si olduğunu belirtir. Ayrıca @Id
ve @GeneratedValue
anotasyonları, bu sınıfın birincil anahtarının (primary key) nasıl oluşturulacağını belirtir.
- Alanlar (Fields): Entity sınıfı, genellikle veritabanı tablosundaki sütunlara karşılık gelen alanları içerir. Yukarıdaki örnekte
id
,name
veemail
alanları bulunmaktadır. - İlişkiler (Relationships): Entity sınıfları, veritabanındaki ilişkileri temsil etmek için kullanılabilir. Örneğin, bir User entity’sinin bir Role entity’siyle ilişkili olması durumunda bu ilişki Java kodu içinde belirtilebilir.
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
@ManyToOne
private Role role;
// Getter ve Setter metotları
}
Yukarıdaki örnek, @ManyToOne
ilişki anotasyonunu kullanarak User entity'sinin bir Role entity'sine (çoktan bire) sahip olduğunu belirtmektedir.
Entity sınıfları genellikle veritabanı tablolarını temsil ederken, JPA ve ORM (Object-Relational Mapping) teknikleri aracılığıyla veritabanı işlemlerini gerçekleştirmek için kullanılır. Bu sınıflar, veritabanındaki verileri Java nesnelerine dönüştürmeye ve Java kodu ile veritabanı arasında etkileşim kurmaya olanak sağlar.

Controller Class’ı
Aşağıdaki örnek, HTTP GET isteklerine yanıt veren basit bir RestController sınıfıdır. /hello
endpoint'ine yapılan isteklere "Merhaba, Dünya!" yanıtı döndürürken, /hello/{name}
endpoint'ine yapılan isteklere isim parametresine göre özelleştirilmiş yanıt döndürmektedir.
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/hello")
public String sayHello() {
return "Merhaba, Dünya!";
}
@GetMapping("/hello/{name}")
public String sayHelloToName(@PathVariable String name) {
return "Merhaba, " + name + "!";
}
}
Service Class’ı
import org.springframework.stereotype.Service;
@Service
public class HelloService {
public String greetUser(String name) {
return "Merhaba, " + name + "!";
}
}
Yukarıdaki örnek, iş mantığını yürüten basit bir servis sınıfıdır. Bu sınıf, kullanıcı adını alır ve bu adı kullanarak özelleştirilmiş bir selam dizesi oluşturur.
Repository Class’ı
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// Özelleştirilmiş sorgular veya işlemler burada tanımlanabilir
}
Yukarıdaki örnek, Spring Data JPA kullanarak veritabanı işlemleri yapmak için JpaRepository’yi genişleten bir UserRepository sınıfını göstermektedir. Bu örnek, User entity’si ile ilişkilendirilmiş bir veritabanı işlemleri arabirimini temsil etmektedir.
Bu örnekler, temel Spring Boot bileşenlerini ve sınıflarını açıklamak için basit bir başlangıç noktası olabilir. Daha karmaşık ve gerçekçi senaryolar için bu temel yapıları geliştirebilir ve uygulamanızın gereksinimlerine göre adapte edebilirsiniz.

Flyway
Flyway, veritabanı şemalarını yönetmek için kullanılan açık kaynaklı bir araçtır. Projemize Flyway’ı entegre ederek veritabanı şemalarını kolayca yönetebiliriz.
Flyway’in sunduğu temel faydalar şunlardır:
1. Veritabanı Sürüm Yönetimi
Flyway, proje geliştirme sürecinde veritabanı şemalarını sürümlemek ve izlemek için kullanılır. Bu, herhangi bir zamanda veritabanı şemasının hangi versiyonda olduğunu görebilmenizi sağlar.
2. Otomatik Migrasyon
Flyway, projeyi başlattığınızda veya yeni bir sürüm dağıttığınızda, veritabanı şemasını otomatik olarak güncellemenize yardımcı olur. Projede yer alan migration dosyalarını otomatik olarak uygulayarak veritabanınızı istenen duruma getirir.
3. Kolay Entegrasyon
Spring Boot, Java, Python, .NET gibi birçok platform ve teknolojiyle uyumlu olarak çalışabilir. Bu, Flyway’i farklı projelerde kolayca entegre edilebilir kılar.
4. Migration Dosyaları ile İşlem İzlenimi
Her migration dosyası, veritabanı şemasındaki bir değişikliği temsil eder. Bu dosyalar adım adım bir geçişleri izlemenizi ve geri dönüş yapmanızı sağlar.
5. Kolay Geri Dönüş (Rollback) (Burası çokomelli!)
Flyway, geri alma (rollback) işlemlerini destekler. Yanlışlıkla uygulanan bir migration dosyasını geri alarak, veritabanınızı önceki bir sürüme getirebilirsiniz.
Özetle, Flyway, yazılım geliştirme sürecinde veritabanı şemalarını yönetmek, izlemek ve güncellemek için kullanılan bir araçtır. Bu, ekip üyelerinin veritabanı değişikliklerini senkronize etmelerine, geri alabilmelerine ve veritabanı şemalarını yönetmelerine yardımcı olur.

İlk olarak, projemize Flyway bağımlılığını eklememiz gerekiyor. Maven veya Gradle gibi bir bağımlılık yöneticisi kullanıyorsanız, bağımlılık tanımını projenize ekleyin:
Maven için pom.xml
dosyasında:
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>{versiyon-numarası}</version> <!-- Kullanmak istediğiniz Flyway sürüm numarasını buraya ekleyin -->
</dependency>
Gradle için build.gradle
dosyasında:
dependencies {
implementation 'org.flywaydb:flyway-core:{versiyon-numarası}' // Kullanmak istediğiniz Flyway sürüm numarasını buraya ekleyin
}
Bu bağımlılığı ekledikten sonra, Flyway’ı yapılandırmak için projenize bir konfigürasyon dosyası eklememiz gerekiyor. Bu dosya, Flyway’ın veritabanı bağlantısı ve migration (geçiş) dosyalarının bulunduğu dizinleri belirtir.
application.properties
dosyasına aşağıdaki gibi bir konfigürasyon ekleyebilirsiniz:
# Flyway config
spring.flyway.locations=classpath:db/migration
spring.flyway.enabled=true
Bu konfigürasyon ile Flyway, proje içindeki db/migration
dizinindeki SQL dosyalarını otomatik olarak algılayacak ve veritabanı üzerindeki geçişleri uygulayacaktır.
db/migration
dizini altında, Flyway'ın çalıştıracağı SQL dosyalarını oluşturabilirsiniz. Örnek olarak, V1__Create_Table.sql
şeklinde dosya adlarıyla ve SQL sorgularıyla migration dosyalarını tanımlayabilirsiniz.
Örneğin:
-- V1__Create_Table.sql
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL
);
Bu şekilde bir migration dosyası oluşturduktan sonra, uygulamayı başlattığınızda Flyway, bu dosyaları veritabanına uygulayacak ve veritabanınızı belirtilen şema düzenine getirecektir.
Şimdi ikinci versiyonda bu tabloya bir kolon eklemek istiyoruz, örneğin, kullanıcıların yaşını tutmak için bir alan ekleyelim:
-- V2__Add_Age_To_User_Table.sql
ALTER TABLE users
ADD COLUMN age INT;
Flyway, bu tür SQL dosyalarını belirli bir sıra ile takip ederek veritabanı şemasını güncellemek için kullanır. Dosyaların adları önemlidir; V{versiyon_numarası}__{Açıklama}.sql
formatında olmalıdır. Bu sayede Flyway, bu dosyaların sıralamasını versiyon numaralarına göre yapabilir.
Örneğin, V1__Create_User_Table.sql
dosyası ilk versiyonu, V2__Add_Age_To_User_Table.sql
dosyası ise ikinci versiyonu temsil eder.
Uygulamayı çalıştırdığınızda Flyway, migration dosyalarını sırayla okuyacak ve henüz uygulanmamış olanları tespit edip uygulayacaktır. Bu sayede veritabanı şemasını güncelleyerek istenen sürüme taşıyacaktır.
Bu adımları tamamladıktan sonra, Spring Boot uygulamanızda Flyway’ı etkinleştirmiş olacak ve veritabanı şemalarınızı yönetebileceksiniz.
Önemli Not: Ben Bu yazıyı yazmaya başladığım zaman PostgreSQL 14 versiyonunu kullanıyordum, şu an 15 kullanıyorum. Yazıya bir süre ara vermiştim, eksik kalmasın istedim. O yüzden sizde buradan bakıp projenize bir şeyler entegre ederken versiyonları gözden geçirebilirsiniz, büyük ihtimalle bir şey olmaz ancak bazı versiyonlar sadece birbirlerinin belirli versiyonlarıyla çalışabiliyor, o yüzden yeni versiyonlarda böyle bir kural geldiyse tek yapmanız gereken güncel versiyon değişikliğine gitmek olabilir.
Yardım Aldığım Kaynaklar
- https://daily-dev-tips.com/posts/installing-postgresql-on-a-mac-with-homebrew/
- https://dba.stackexchange.com/questions/198125/sudo-u-postgres-psql-postgres-does-not-ask-for-password
- https://stackoverflow.com/questions/36155219/psql-command-not-found-mac
- https://stackoverflow.com/questions/38249434/docker-postgres-failed-to-bind-tcp-0-0-0-05432-address-already-in-use
- https://thoughtbot.com/blog/macos-postgres-could-not-connect-to-server
- https://dzone.com/articles/build-a-spring-boot-app-with-flyway-and-postgres