Geliştirme sürecinin başlangıcında iyi bir klasör yapısı oluşturmak, proje boyunca kodun sürdürülebilirliğini artırmak, ekip üyeleri arasındaki işbirliğini kolaylaştırmak ve genel yazılım geliştirme sürecini çevikleştirmek için hayati öneme sahiptir. İyi bir klasör yapısı, kodun düzenli ve anlaşılır olmasını sağlar ve böylece geliştirme süreci boyunca karmaşıklığı azaltır, hataları da önler.
İyi bir klasör yapısı oluşturmanın faydaları şöyle sıralanabilir;
- Sürdürülebilirlik ve Kolay Bakım: İyi organize edilmiş bir klasör yapısı, kodunuzu kolayca anlaşılabilir ve bakımı yapılabilir hale getirir. Kodunuzun sürdürülebilir olması, üzerinde değişiklik yapmanızı ve yeni özellikler eklemenizi kolaylaştırır. Ayrıca uygun bir klasör yapısına sahip olmak, bu bileşenleri etkili bir şekilde organize etmenize olanak tanıyarak uygulamanın anlaşılmasını ve bakımını kolaylaştırır.
- Ekip Üyeleri Arasında İşbirliği: Ortak bir klasör yapısı, ekip üyeleri arasında işbirliğini artırır. Herkesin aynı klasör yapısına sahip olması, kodun paylaşılmasını, anlaşılmasını, tutarlılığı ve düzenlenmesini kolaylaştırır. Böylece ekipteki herkes yeni kodun nereye yerleştirileceğini bilir ve başkaları tarafından eklenen kodu hızla bulabilir.
- Kodun Bulunabilirliği ve Erişilebilirliği: İyi organize edilmiş bir klasör yapısı, kod parçalarının hızlı bir şekilde bulunmasını ve erişilmesini sağlar. Bu da zaman kaybını önler ve geliştirme sürecini hızlandırır.
- Yeni Ekip Üyelerini Eklemek: Ekibe yeni geliştiriciler katıldığında, iyi tanımlanmış bir klasör yapısı sayesinde hızlı bir şekilde adapte olmalarına yardımcı olur. Mimariyi hızlı bir şekilde anlayabilirler ve ilgili kod bölümlerini hızlıca bulabilirler.
- Standartlaşma ve Konsistensizlik: Ortak bir klasör yapısı, projenin farklı bölümlerinde tutulan dosyalar arasında standartlaşma sağlar. Bu da konsistensizlikleri azaltır ve kodun genel kalitesini artırır.
- Entegrasyon Kolaylığı: İyi organize edilmiş bir klasör yapısı, farklı modüller arasında entegrasyonun daha kolay olmasını sağlar. Bu da farklı parçaların bir araya getirilmesini ve birbiriyle uyumlu bir şekilde çalışmasını kolaylaştırır.
- Sürdürülebilir Kod Tabanı: Temiz ve iyi yapılandırılmış bir kod tabanının uzun vadede bakımı daha kolaydır. Teknik borcu azaltır ve değişiklik yaparken hata veya gerileme ortaya çıkma olasılığını azaltır.
- Test Edilebilirlik: Klasör yapısının düzenli olması, testlerin yazılmasını ve uygulanmasını kolaylaştırır. Bu da yazılımın kalitesini artırır ve hataların erken tespit edilmesine yardımcı olur.
Bu nedenlerden dolayı, yazılım geliştirme sürecinde iyi organize edilmiş bir klasör yapısının kullanılması önemlidir. Bu yapı, hem mevcut projenin bakımını kolaylaştırır hem de gelecekteki geliştirmeler için temel oluşturur.
Spring Boot için iyi tasarlanmış klasör yapısına şöyle bir örnek verilebilir;
src/
├── main/
│ ├── java/
│ │ └── com/
│ │ └── example/
│ │ └── demo/
│ │ ├── config/ # Uygulama yapılandırma sınıfları
│ │ ├── controller/ # REST API kontrolcüleri
│ │ ├── dto/ # Data Transfer Object (DTO) sınıfları
│ │ ├── entity/ # Veritabanı varlıkları (Entity sınıfları)
│ │ ├── repository/ # Spring Data JPA repository sınıfları
│ │ ├── service/ # Servis katmanı sınıfları
│ │ └── DemoApplication.java # Uygulama giriş noktası (SpringBootApplication sınıfı)
│ ├── resources/
│ │ ├── static/ # Statik dosyalar (CSS, JS, vb.)
│ │ ├── templates/ # HTML şablon dosyaları
│ │ └── application.properties # Uygulama yapılandırma dosyası
└── test/
└── java/
└── com/
└── example/
└── demo/
└── service/ # Servis katmanı test sınıfları
Spring Boot projemizin ve React uygulamamızın birlikte geliştirilmesi için tipik bir proje dizin yapısı ise şu şekilde olabilir:
project-root/
├── backend/ # Backend Spring Boot uygulaması
│ ├── src/ # Kaynak kod dosyaları
│ ├── target/ # Derlenmiş kod ve diğer çıktı dosyaları
│ ├── pom.xml # Maven projesi yapılandırma dosyası
│ └── ...
├── frontend/ # Frontend React uygulaması
│ ├── public/ # Genel dosyalar (favicon.ico, index.html, vb.)
│ ├── src/ # React uygulama kaynak kodları
│ ├── node_modules/ # Bağımlılıklar (npm veya yarn)
│ ├── package.json # Proje bağımlılıklarını ve scriptlerini içeren dosya
│ ├── yarn.lock # Bağımlılıkların sabitlenmiş sürümlerini içeren dosya (yarn kullanılacaksa...)
│ ├── .gitignore # Git yoksayılan dosyaları ve klasörleri
│ └── ...
├── .gitignore # Git yoksayılan dosyaları ve klasörleri
└── README.md # Proje hakkında genel bilgiler ve belgeler
Bu yapı, hem backend hem de frontend kodlarını ayrı klasörlerde tutarak projenin düzenli ve bakımı kolay olmasını sağlar. Ayrıca, her iki klasörün de kendi bağımsız yapılandırma dosyalarına (örneğin, pom.xml ve package.json) sahip olması, her iki bileşenin de kendi gereksinimlerini yönetmesini sağlayacktır.
İlk etapta dosya dizini yapısı üzerinde çok fazla durmanıza gerek yok. Geliştirme yaptıkça, yeni projelere başladıkça bu konu kendiliğinden oturacaktır.
Şimdi basit bir entity class oluşturarak yavaş yavaş geliştirmeye başlayalım.
Genellikle bir e-ticaret veya envanter yönetimi uygulamasında ürünlerin temsil edildiği bir veritabanı tablosuna karşılık gelen Product entity si oluşturalım;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Entity
@Table(name = "products"
public class Product {
private Long id;
private String name;
private double price;
private int stockQuantity;
}Projeyi bu haliyle çalıştırıp bir bakalım;

Görüldüğü gibi hata almaktayız. Normalde Eclipse veya intellij idea kullansaydık “Persistent Entity ‘Product’ should have primary key” hatasını derlerken alacaktık. Fakat sorun değil. Projeyi çalıştırınca console çıktısında da belirtiği üzere bir primary key özelliği belirlememiz gerekiyor. Belirleyelim;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private double price;
private int stockQuantity;
}Bu örnek entity sınıfımız, artık bir Product entity i temsil ediyor. Bu bağlamda yeri gelmişken kullanmış olduğumuz anotasyonlara değinmek istiyorum:
@Entityve@Tableanotasyonları, bu sınıfın bir JPA entity’si olduğunu ve “products” adlı bir veritabanı tablosuna karşılık geldiğini belirtir. Yani artık bu haliyle beraber DB ye bağlanabiliriz.@Idve@GeneratedValueanotasyonları, “id” alanının birincil anahtar (primary key) olduğunu ve otomatik artan bir değer olduğunu belirtir.@Columnanotasyonları, her alanın bir veritabanı sütunuyla eşleştiğini ve gerekli özellikleri belirtir.
Şimdi projemizi başlatabilir ve herhangi bir hata almadığımızı görebilirsiniz.
Kolay ve anlaşılır olması açısından H2DB düzleminde işlemler yapacağız. Bu nedenle application.properties içerisine H2DB ayarlarını ekleyelim ve kısa bir göz gezdirelim;
logging.level.org.springframework.jdbc.core = TRACE
spring.jpa.properties.hibernate.format_sql = true
spring.jpa.properties.hibernate.show_sql = true
spring.jpa.show-sql=true
#jpa > none update create, create-drop, validate
spring.jpa.hibernate.ddl-auto = create
spring.h2.console.settings.web-allow-others=true
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.url=jdbc:h2:file:./db/e-commerce
spring.datasource.username=root
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect- logging.level.org.springframework.jdbc.core = TRACE: Spring Framework’ün JDBC core paketi için log seviyesini TRACE olarak ayarlar. Bu, JDBC ile yapılan sorguların ayrıntılı loglarını sağlar. Yani JDBC Core ile alakalı ne oluyorsa consol da gösterilcek.
- spring.jpa.properties.hibernate.format_sql = true: Hibernate tarafından oluşturulan SQL sorgularının güzel bir şekilde biçimlendirilmesini sağlar. Bu, geliştirme ve hata ayıklama sürecinde okunabilirliği artırır.
- spring.jpa.properties.hibernate.show_sql = true: Hibernate tarafından oluşturulan SQL sorgularını konsola yazdırır. Bu, gerçekleştirilen SQL sorgularını doğrudan görmeyi sağlar.
- spring.jpa.show-sql=true: Hibernate tarafından oluşturulan SQL sorgularının konsola yazdırılmasını sağlar.
spring.jpa.properties.hibernate.show_sqlile aynı işlevi yerine getirir. - spring.jpa.hibernate.ddl-auto = create: JPA ve Hibernate tarafından veritabanının oluşturulma stratejisini belirler. Bu ayar, uygulama başlatıldığında var olan tabloları yeniden oluşturur. Daha sonra
create-dropveyavalidategibi başka değerlere değiştirilebilir. - spring.h2.console.settings.web-allow-others=true: H2 konsoluna diğer cihazlardan erişime izin verir. Bu, uygulamanın çalıştığı ortamda H2 konsolunu kullanmak için faydalı olabilir.
- spring.h2.console.enabled=true: H2 konsolunu etkinleştirir. Bu, uygulamanın çalıştığı ortamda H2 konsoluna erişimi sağlar.
- spring.h2.console.path=/h2-console: H2 konsolunun erişim yolunu belirler. Bu, tarayıcınızda H2 konsoluna erişmek için kullanılacak URL’yi belirler.
- spring.datasource.driverClassName=org.h2.Driver: Kullanılan JDBC sürücüsünü belirtir. Bu, H2 veritabanı için varsayılan sürücüyü belirtir.
- spring.datasource.url=jdbc:h2:file:./db/e-commerce: Veritabanının bağlanılacak URL’sini belirtir. Bu örnekte,
./db/e-commerceadlı bir H2 veritabanı dosyasına bağlanılır. Projeyi çalıştırdığımzıda db adında klasörün oluştuğunu göreceksiniz. - spring.datasource.username=root: Veritabanına bağlanmak için kullanılacak kullanıcı adını belirtir.
- spring.datasource.password=: Veritabanına bağlanmak için kullanılacak şifreyi belirtir.
- spring.jpa.database-platform=org.hibernate.dialect.H2Dialect: Kullanılan veritabanı için Hibernate tarafından kullanılacak SQL dilini belirtir. Bu, H2 veritabanı için uygun olan bir SQL diyalektini belirtir.
Projeyi yeniden başlattıktan sonra kök dizininde db adında klasörün oluştuğunu göreceksiniz. Şimdi konsol çıktımıza bir bakalım;

Hibernate tarafından yürütülen SQL sorgularını görmekteyiz. Veritabanı yapılandırmasındaki spring.jpa.hibernate.ddl-auto özelliği create olarak ayarlandığında, Hibernate, veritabanı şemasını oluşturmak için uygun SQL sorgularını yürütür.
Hibernate tarafından oluşturulan sorguya bakacak olursak:
- İlk olarak,
drop table if exists products cascadesorgusu var. Bu sorgu, “products” adlı tablonun varsa silinmesini sağlar.cascadeifadesi, bu tabloya bağlı diğer tablolar varsa, onları da siler. - Ardından,
create table productssorgusu geliyor. Bu sorgu, “products” adında yeni bir tablo oluşturur. Tablo,price,stock_quantity, venameadlarında sütunlar içerir.idsütunu, birincil anahtar (primary key) olarak ayarlanır ve otomatik artan bir değer olarak tanımlanır.
Bu sorgular, spring.jpa.hibernate.ddl-auto = create ayarıyla belirtilen (hibernate.hbm2ddl.auto) stratejisini izleyerek, var olan tablaları düşürür ve ardından belirtilen entity sınıflarına göre yeni tablolar oluşturur. Bu ayarı application.properties de yapmıştık.
Şimdi H2DB console bağlanıp, tablomuzun oluşup oluşmadığını kontrol edelim. application.properties de H2DB için path belirlemiştik. Tarayıcımızdan http://localhost:port/h2-console gidip kontrollerimizi yapabiliriz.


Görüldüğü üzere Products adında tablomuz oluşmuş. Tabloya ait alanlarımızın da geldiği görülmekte.
@Column anotasyonuna ve kullanım senaryolarına bakacak olursak;
/*
Bu temel kullanım, "name" alanını bir veritabanı sütunuyla eşleştirir.
Varsayılan olarak, alan adı ve veri tipi kullanılarak veritabanında bir sütun oluşturulur
*/
@Column
private String name;
/*
name özelliğiyle, alanın veritabanındaki sütun adını, "product_name" adlı
bir sütunla eşleşmesini sağlar
*/
@Column(name = "product_name")
private String name;
/*
"description" alanının maksimum 100 karakterlik bir sütunla eşleşmesini sağlar
*/
@Column(length = 100)
private String description;
/*
sütununun benzersiz olmasını sağlar
*/
@Column(unique = true, columnDefinition = "varchar(255) 'Açıklama..' ")
private String email;
/*
alanın veritabanındaki sütununun null olup olamayacağını belirler
*/
@Column(nullable = false)
private Integer quantity;
/*
alanın, varsayılan olarak geçerli bir tarih ve saat olarak ayarlanmış
bir TIMESTAMP türünde sütunla eşleşmesini sağlar
*/
@Column(name = "date_created", nullable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
private LocalDateTime createdAt;
/*
alana denk gelen stüna birden fazla özellik de eklebilir.
@Column(name = "deneme", updatable = false, nullable = false, unique = true)
*/
@Column(name = "created_date", nullable = false)
@CreationTimestamp //ne zaman veri eklenirse, anlık sistem zamanını alır ve ekler
@Temporal(TemporalType.TIMESTAMP) //zaman bilgisini belirledik
private Date createdDate;
/*
Bazen biz javada bulundurduğumuz özelliği db ye kaydettirmeyebiliriz veya kaydettirmek istemeyebiliriz.
@Transient ile işaretolenen alanlar, jpa tarafından bir entity sınıfı olarak tanımlanırken dikkate alınmazlar
dolayısıyla veritabanında bu alanlar için bir sütun oluşturulmaz ve
sorguları çalıştırırken de dikkate alınmaz.
Örneğin, bir hesaplama sonucu veya geçici bir işlem sonucu veriyi saklamak
için bu notasyonu kullanabilirsiniz.
*/
@Transient
private String justJavaDataSpecial;Projenize göre class içeriği daha da genişletilebilir, ilişkili tablolar oluşturulabilirsiniz. Daha detaylı kullanım örnekleri için https://www.baeldung.com/jpa-entities sayfasına göz atabilirsiniz.
Bu temel yapı, daha karmaşık bir Product entity oluşturmak için bir başlangıç noktası olabilir.
Sonraki yazımda Product için Repository oluşturarak çeşitli veritabanı işlemlerini gerçekleştireceğiz.
Sorularınız için yorum bırakabilir veya buraya tıklayarak benimle iletişime geçebilirsiniz.
İyi çalışmalar dilerim.