Altıgen Mimari etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
Altıgen Mimari etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

Spring, Spring Data, Java 11, H2 DB ve Hibernate ile Rich domain model kullanarak aggregateler value objectler entityler kullanarak advanced bir senaryo ile örnek bir senaryo ile DDD projesini açıklayarak kodla. Port ve Adaptör ile Kafka ve Hazelcast Cache Entegrasyonunu sağla. Event Sourcing ekle.




Product Entity - Stok Kontrolü Dahil


@Entity

public class Product {


    @Id

    @GeneratedValue(strategy = GenerationType.IDENTITY)

    private Long id;

    private String name;

    private double price;

    private int stockQuantity;


    public void decreaseStock(int quantity) {

        if (stockQuantity < quantity) {

            throw new IllegalStateException("Not enough stock");

        }

        stockQuantity -= quantity;

    }


    public void increaseStock(int quantity) {

        stockQuantity += quantity;

    }


    // Diğer alanlar ve metodlar

}

Order Entity - Sipariş Durumu Değişiklikleri Dahil

@Entity

public class Order {


    @Id

    @GeneratedValue(strategy = GenerationType.IDENTITY)

    private Long id;


    @Enumerated(EnumType.STRING)

    private OrderStatus status;


    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)

    private List<OrderLine> orderLines;


    public void place() {

        if (status != OrderStatus.NEW) {

            throw new IllegalStateException("Can only place a new order");

        }

        status = OrderStatus.PLACED;

    }


    public void ship() {

        if (status != OrderStatus.PLACED) {

            throw new IllegalStateException("Can only ship a placed order");

        }

        status = OrderStatus.SHIPPED;

        for (OrderLine line : orderLines) {

            line.getProduct().decreaseStock(line.getQuantity());

        }

    }


    public void cancel() {

        if (status != OrderStatus.PLACED) {

            throw new IllegalStateException("Can only cancel a placed order");

        }

        status = OrderStatus.CANCELLED;

        for (OrderLine line : orderLines) {

            line.getProduct().increaseStock(line.getQuantity());

        }

    }


    // Diğer alanlar ve metodlar

}

Bu, daha zengin bir domain modeli oluşturur. İş mantığı, servis katmanından alınıp model nesnelerine yerleştirilmiştir. Bu yaklaşım, daha temiz bir kod ve model içerisinde daha çok iş mantığını ifade eder.


Tamam, bu zengin domain modeli etrafında servis ve repository katmanlarını oluşturalım.


Repository Katmanı

Önceki açıklamalarda zaten tanımlanan repository sınıflarını kullanabiliriz (ProductRepository, OrderRepository vb.). Fakat bu modelde iş mantığı daha çok domain katmanında olduğu için, repository'nin rolü veritabanı işlemlerini gerçekleştirmekle sınırlıdır.


Servis Katmanı

Zengin domain modeliyle birlikte servis katmanının karmaşıklığı azalır. İş mantığı artık modelde yer aldığı için servis katmanı daha çok depolama ve altyapı işlevleriyle ilgilenir.


ProductService


@Service

public class ProductService {


    @Autowired

    private ProductRepository productRepository;


    public Product findById(Long id) {

        return productRepository.findById(id).orElse(null);

    }


    public Product save(Product product) {

        return productRepository.save(product);

    }

}

OrderService


@Service

public class OrderService {


    @Autowired

    private OrderRepository orderRepository;


    @Autowired

    private ProductService productService;


    public List<Order> findAll() {

        return orderRepository.findAll();

    }


    public Order findById(Long id) {

        return orderRepository.findById(id).orElse(null);

    }


    public Order placeOrder(Order order) {

        for (OrderLine line : order.getOrderLines()) {

            Product product = productService.findById(line.getProduct().getId());

            if (product == null) {

                throw new IllegalArgumentException("Product not found");

            }

            product.decreaseStock(line.getQuantity());

            productService.save(product);

        }

        order.place();

        return orderRepository.save(order);

    }


    public void shipOrder(Long orderId) {

        Order order = findById(orderId);

        if (order == null) {

            throw new IllegalArgumentException("Order not found");

        }

        order.ship();

        orderRepository.save(order);

    }


    public void cancelOrder(Long orderId) {

        Order order = findById(orderId);

        if (order == null) {

            throw new IllegalArgumentException("Order not found");

        }

        for (OrderLine line : order.getOrderLines()) {

            Product product = productService.findById(line.getProduct().getId());

            product.increaseStock(line.getQuantity());

            productService.save(product);

        }

        order.cancel();

        orderRepository.save(order);

    }

}

Bu yapıda, OrderService ve ProductService sınıfları daha sade bir yapıya sahip. Modelde bulunan iş mantığı sayesinde servisler daha az sorumluluğa sahip.


Controller Katmanı

Controller katmanı da önceki şekilde kalabilir. Fakat artık bazı endpoint'lerde doğrudan servislerin yerine modelde tanımlanan iş mantığına dayalı olarak işlemleri gerçekleştirebilirsiniz.


Sonuç olarak, bu yaklaşımla, iş mantığı daha çok domain katmanında bulunurken, servis ve controller katmanları daha az karmaşık ve daha az işlevsellik taşır. Bu sayede kodun okunabilirliği, bakımı ve genişletilmesi daha kolay olur.


Bu aşamadan sonra eklememi veya açıklamamı istediğiniz başka bir konu var mı?


Bu uygulamaya adaptörler ve portlar kullanarak apache kafka entegre edebilir misin?


Elbette, Domain-Driven Design (DDD) yaklaşımında kullanılan hexagonal architecture (ya da ports and adapters architecture) sayesinde, uygulamanın çekirdeğini (domain) dış dünyadan (örneğin, Kafka gibi mesajlaşma sistemlerinden) izole edebiliriz.


Apache Kafka, veri yayını ve tüketimi için kullanılan açık kaynaklı bir stream-processing yazılım platformudur. Bu örnek için, sipariş durumlarının değişikliklerini Kafka'ya göndermek ve bu değişiklikler üzerinde dinleyiciler oluşturmak istediğimizi varsayalım.


1. Ports Tanımlama

Portlar, uygulamanın iç ve dış dünyası arasında bir sınır oluşturur. İlk olarak, bir port tanımlayarak bu sınırı ifade edelim:


public interface OrderStatusChangedPort {

    void orderStatusChanged(Long orderId, OrderStatus status);

}

Bu port, sipariş durumu değiştiğinde çağrılacak olan metodu tanımlar.


2. Kafka Adapter Oluşturma

Bu port için Kafka ile entegrasyon sağlayan bir adapter yazmalıyız. Bu adapter, Kafka'ya mesaj gönderecek olan bileşendir.


@Service

public class KafkaOrderStatusChangedAdapter implements OrderStatusChangedPort {


    private final KafkaTemplate<String, String> kafkaTemplate;


    @Value("${kafka.topic.order-status}")

    private String orderStatusTopic;


    @Autowired

    public KafkaOrderStatusChangedAdapter(KafkaTemplate<String, String> kafkaTemplate) {

        this.kafkaTemplate = kafkaTemplate;

    }


    @Override

    public void orderStatusChanged(Long orderId, OrderStatus status) {

        String message = "Order ID: " + orderId + ", Status: " + status.name();

        kafkaTemplate.send(orderStatusTopic, message);

    }

}

3. Domain Katmanında Port Kullanma

OrderService içerisinde, sipariş durumu değiştikçe port'u kullanarak bu durumu dış dünyaya bildireceğiz:


@Service

public class OrderService {


    @Autowired

    private OrderStatusChangedPort orderStatusChangedPort;


    // Diğer metotlar


    public void shipOrder(Long orderId) {

        Order order = findById(orderId);

        if (order == null) {

            throw new IllegalArgumentException("Order not found");

        }

        order.ship();

        orderRepository.save(order);

        orderStatusChangedPort.orderStatusChanged(orderId, OrderStatus.SHIPPED);

    }


    public void cancelOrder(Long orderId) {

        Order order = findById(orderId);

        if (order == null) {

            throw new IllegalArgumentException("Order not found");

        }

        order.cancel();

        orderRepository.save(order);

        orderStatusChangedPort.orderStatusChanged(orderId, OrderStatus.CANCELLED);

    }

}

4. Kafka Konfigürasyonu

Son olarak, Kafka'yı Spring uygulamanızda kullanmak için gerekli konfigürasyonları eklemelisiniz. application.properties dosyasına şunları ekleyebilirsiniz:


spring.kafka.bootstrap-servers=localhost:9092

kafka.topic.order-status=order-status-topic

Ve bir KafkaConfig sınıfı oluşturarak gereken bean'leri tanımlayabilirsiniz:


@Configuration

public class KafkaConfig {


    @Value("${spring.kafka.bootstrap-servers}")

    private String bootstrapServers;


    @Bean

    public KafkaTemplate<String, String> kafkaTemplate() {

        return new KafkaTemplate<>(producerFactory());

    }


    @Bean

    public ProducerFactory<String, String> producerFactory() {

        Map<String, Object> configProps = new HashMap<>();

        configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);

        configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);

        configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);

        return new DefaultKafkaProducerFactory<>(configProps);

    }

}

Bu yapı, Apache Kafka ile entegre bir şekilde sipariş durumu değişikliklerini izlemenizi sağlar. Bu durum değişikliklerine dinleyiciler eklemek veya daha fazla entegrasyon oluşturmak da mümkündür.


Bu yaklaşımın güzel yanı, Kafka ile entegrasyonun tamamen izole edilmesi ve domain katmanının bu ayrıntılardan haberdar olmamasıdır. Bu, kodun daha temiz, bakımı daha kolay ve değişikliklere karşı daha esnek olmasına yardımcı olur.


Projeye port ve adaptör ile Hazelcast cache ekle


Tabii ki! Hazelcast, dağıtık bir veri ızgarası ve önbellekleme çözümüdür. Hexagonal mimaride, Hazelcast ile entegrasyonu port ve adaptörler aracılığıyla yapabiliriz. Bu sayede, iş mantığınızın önbellekleme mantığından tamamen izole edilmesi sağlanır.


1. Cache Portu Tanımlama

İlk olarak, önbellekleme işlemlerini tanımlayacak bir port oluşturmalıyız. Bu port, önbelleğe erişmek istediğimiz işlemleri ifade eder.


public interface CachePort {

    <T> T get(String key);


    void put(String key, Object value);


    void remove(String key);

}

2. Hazelcast Cache Adaptörü Oluşturma

Bu port için Hazelcast ile entegrasyon sağlayan bir adaptör yazmalıyız. Adaptör, portun metotlarını Hazelcast'a özgü çağrılara dönüştürür.


@Service

public class HazelcastCacheAdapter implements CachePort {


    private final HazelcastInstance hazelcastInstance;


    @Autowired

    public HazelcastCacheAdapter(HazelcastInstance hazelcastInstance) {

        this.hazelcastInstance = hazelcastInstance;

    }


    @Override

    public <T> T get(String key) {

        return (T) hazelcastInstance.getMap("default").get(key);

    }


    @Override

    public void put(String key, Object value) {

        hazelcastInstance.getMap("default").put(key, value);

    }


    @Override

    public void remove(String key) {

        hazelcastInstance.getMap("default").remove(key);

    }

}

3. Hazelcast Konfigürasyonu

HazelcastInstance bean'ini oluşturmak için bir konfigürasyon sınıfı eklemeliyiz:


@Configuration

public class HazelcastConfig {


    @Bean

    public Config hazelCastConfig() {

        Config config = new Config();

        config.setInstanceName("hazelcast-instance")

              .addMapConfig(new MapConfig()

                    .setName("default")

                    .setTimeToLiveSeconds(20));

        return config;

    }


    @Bean

    public HazelcastInstance hazelcastInstance(Config hazelCastConfig) {

        return Hazelcast.newHazelcastInstance(hazelCastConfig);

    }

}

4. Servis Katmanında Cache Kullanma

Şimdi, önbellek portunu servis katmanında kullanabiliriz. Örnek olarak, siparişlerin detaylarını önbelleklemek isteyebiliriz:

@Service

public class OrderService {


    @Autowired

    private CachePort cachePort;


    public OrderDetail getOrderDetail(Long orderId) {

        String key = "orderDetail:" + orderId;

        OrderDetail orderDetail = cachePort.get(key);

        if (orderDetail == null) {

            orderDetail = // Veritabanından sipariş detaylarını alın.

            cachePort.put(key, orderDetail);

        }

        return orderDetail;

    }

}

Bu yapı sayesinde, Hazelcast veya başka bir önbellekleme çözümüne geçiş yapmak istediğinizde, yalnızca yeni bir adaptör yazmanız gerekir. Bu, uygulamanın diğer kısımlarının değişiklik yapmadan aynı port üzerinden çalışmasına devam etmesini sağlar.


Event Sourcing de ekler misin?


Tabii ki, Event Sourcing'u bu mimariye eklemek ilginç ve karmaşık bir yön olabilir. Event Sourcing, her değişikliği olay (event) olarak saklamak yerine, uygulamanın durumunu bu olaylardan yeniden oluşturmanızı sağlar. Bu, daha güçlü bir denetim günlüğü ve karmaşık iş süreçlerinde daha fazla esneklik sağlayabilir.


Bu örnekte, sipariş durum değişikliklerinin olaylarını saklayacağımızı varsayalım.


1. Olay Tanımları

Her bir olay, belirli bir değişikliği temsil eder. Bu örnekte, sipariş durumlarının değiştiği olayları tanımlayalım:


public abstract class OrderEvent {

    private final Long orderId;

    private final LocalDateTime timestamp;


    protected OrderEvent(Long orderId) {

        this.orderId = orderId;

        this.timestamp = LocalDateTime.now();

    }


    public Long getOrderId() {

        return orderId;

    }


    public LocalDateTime getTimestamp() {

        return timestamp;

    }

}


public class OrderPlacedEvent extends OrderEvent {

    public OrderPlacedEvent(Long orderId) {

        super(orderId);

    }

}


public class OrderShippedEvent extends OrderEvent {

    public OrderShippedEvent(Long orderId) {

        super(orderId);

    }

}


public class OrderCancelledEvent extends OrderEvent {

    public OrderCancelledEvent(Long orderId) {

        super(orderId);

    }

}

2. Event Store

Olayları saklamak için bir olay deposu oluşturmalıyız. Bu depo, olayların saklanmasından ve sorgulanmasından sorumlu olacak.


@Repository

public interface EventStore extends JpaRepository<OrderEvent, Long> {

    List<OrderEvent> findByOrderIdOrderByTimestampAsc(Long orderId);

}

3. Event Publisher

Olayları yayınlayan bir sınıf oluşturacağız:


@Service

public class EventPublisher {


    @Autowired

    private EventStore eventStore;


    public void publish(OrderEvent event) {

        eventStore.save(event);

    }

}

4. Aggregate Root İle İntegrasyon

Order sınıfı, siparişle ilgili olayları yayınlamalı. Bu, Aggregate Root'un görevidir.


@Entity

public class Order {


    // Diğer alanlar


    @Transient

    private List<OrderEvent> newEvents = new ArrayList<>();


    public void place() {

        // Logic

        newEvents.add(new OrderPlacedEvent(this.id));

    }


    public void ship() {

        // Logic

        newEvents.add(new OrderShippedEvent(this.id));

    }


    public void cancel() {

        // Logic

        newEvents.add(new OrderCancelledEvent(this.id));

    }


    public List<OrderEvent> getNewEvents() {

        return Collections.unmodifiableList(newEvents);

    }

}

5. Event Sourcing ile Servis Katmanı İntegrasyonu

Servis katmanında, olayları toplayıp yayınlamalıyız:



@Service

public class OrderService {


    @Autowired

    private EventPublisher eventPublisher;


    // Diğer metotlar


    @Transactional

    public void shipOrder(Long orderId) {

        Order order = findById(orderId);

        order.ship();

        orderRepository.save(order);

        order.getNewEvents().forEach(eventPublisher::publish);

        // Diğer işlemler

    }

}

Bu yapı, her sipariş durum değişikliğinin bir olay olarak saklanmasını ve daha sonra bu olayların sorgulanmasını veya farklı hedeflere (örneğin, bir mesaj kuyruğuna) yayınlanmasını sağlar.


Event Sourcing, karmaşık iş süreçlerinin ve denetim gereksinimlerinin olduğu durumlarda çok yararlı olabilir. Bununla birlikte, olayların tutarlı bir şekilde yayınlanması ve saklanması gibi bazı zorluklar da getirir.

Rastgele İçerik

DonanımHaber

© tüm hakları saklıdır
made with by templateszoo