"Dünyanın en büyük ücretsiz öğrenme platformuna hoş geldiniz!
Khan Academy eğitimde fırsat eşitliğini güçlendirmek için herkese, her yerde, dünya standartlarında ve ücretsiz bir öğrenim imkanı sağlamayı amaçlayan ve kar amacı gütmeyen uluslararası bir eğitim kuruluşudur. Tek bir şeyi bilmelisin: Her Şeyi Öğrenebilirsin! Şu anda Khan Academy Türkçe'nin resmi YouTube kanalındasınız, binlerce ders videosundan oluşan kütüphanemizi gezmek için web sitemizi ziyaret edebilirsiniz: www.khanacademy.org.tr #KhanAcademyTürkçe #DünyaOkulu #HerŞeyiÖğrenebilirsin #EğitimdeFırsatEşitliği #KhanAcademyTR * * * Khan Academy, bir STFA Vakfı olana Bilimsel ve Teknik Yayınları Çeviri Vakfı çatısı altında yüzlerce gönüllünün katkılarıyla Türkçeleştirilmektedir. Daha çok eğitim içeriğinin dilimize çevrilmesine ya da Khan Academy'nin ücretsiz bir kaynak olarak daha çok insana ulaşmasına destek olmak isterseniz, gönüllü olmak için bize ulaşın: info@khanacademy.org.tr"Yazılım mühendisleri için davranışsal görüşme soruları (Behavioral Interviews: for Software Engineers)
Merhaba bu yazımda sizlere, teknik mülakattan önce işveren için adayı tanıma konusunda yol gösterici olan, davranışsal interview sorularına biraz kendi mesleki hayatımdan ve bazen kurgusal olarak örnekler vermeye çalışacağım.
Davranışsal görüşmede nasıl başarılı olunur?
Davranışsal sorular ve cevaplar ingilizce olacak, ayrıca Türkçe açıklama ve çeviriler eklenecektir.
01. Tell me about one of the most technically challenging projects you have done.
(Bitirmiş olduğunuz, teknik olarak en zorlu projelerden birini anlatın.)
Answer: Most of the projects that I was involved in were technically challenging. But if I had to choose one I would say Digital Agency Project in Anadolu Sigorta was the most challenging. When we started this project, the technologies we were using were just becoming popular in 2017 (Jhipster, Spring Boot, Angular 4, Elasticearch, Yarn, Liquebase, Mapstruct) and we didn't have too much knowledge about these technologies and we had to integrate them with Anadolu Sigorta SOA services. The project domain was also complex.
02. Tell me about one of your failed projects. What did you learn? What could you do differently?
(Bana başarısız projelerinizden birini anlatın. Ne öğrendin? Neyi farklı yapabilirdin?)
Answer: I can give an example of a big task of a project where things didn't go exactly the way I wanted. In Garanti Investment web project financial dictionary implementation was an important part of the project. I was not as well skilled at javascript as backend technologies. And I didn't have a lot of knowledge of clean code nor was I aware of its importance. I successfully finished the dictionary implementation, but it took too long and it hadn't been implemented with clean code principles and effective way. Our team leader decided to implement it from the scratch by himself.
After that completion of the task, I realized the importance of clean code and writing effective javascript code.
03. Tell me about the project that you are most proud of. What was the most significant accomplishment of your entire career?
(Bana en çok gurur duyduğun projeden bahset. Tüm kariyerinizin en önemli başarısı neydi?)
Answer: If had to choose one I would say the project I am most proud of was emlakjet.com rebuilding project. We analysed the legacy project, understood the domain, migrated the database from MySQL to PostgreSQL, implemented the project with the modern framework, and completed the project successfully before the deadline.
04. Tell me about a time that you found a creative solution to a problem.
(Bana bir soruna yaratıcı bir çözüm bulduğunuz zamanı anlatın.)Answer: In my last project RiskMobile, we had performance and memory issues. We came up with some solutions for these :
Increased free heap memory space by up to %50 calculating report String size before transforming base64 StringBuilder.
Increased report photo upload speed by up to %80 using the parallel upload.
An increased report read time up to %80 by lazy loading.
05. Tell me about a time when you had a conflict with your teammate or manager: how did you resolve it, and what did you learn?
(Takım arkadaşın veya yöneticinle bir çatışma yaşadığın bir zamandan bahset: Bunu nasıl çözdün ve ne öğrendin?)
Answer: Usually, conflicts happen between analysts and developers. In such cases, I have a private conversation with the analyst to try to understand him/her and tell him/her about my task-related dilemmas. I try to find a solution together without making it a personal issue.
06. Tell me about a time that you were behind on a project and you knew that you could not meet the deadline. Tell me about a time when you changed priorities to meet a deadline.
(Bana bir projede geride kaldığınız ve son teslim tarihine yetişemeyeceğinizi bildiğiniz bir zamandan bahsedin. Son teslim tarihine uymak için önceliklerinizi değiştirdiğiniz bir zamandan bahsedin.)
Answer: In a new version of the Alcatel OSOS project, we had defects to solve and new features to add. Towards the end date, we were far behind these goals. We have postponed less critical new features to the next version and put it first to solve problems that matter to the customer. After solving the problems, we applied the most valuable new features and successfully released the version.
07. Tell me about a time that you had to implement a workaround (vs. a solution) for a critical issue to meet a deadline and as a result, you introduced technical debt. What did you do with the technical debt after the deadline?
(Son teslim tarihini karşılamak için kritik bir sorun için bir geçici çözüm (çözüm yerine) uygulamak zorunda olduğunuz ve bunun sonucunda teknik borç getirdiğiniz bir zamandan bahsedin. Son teslim tarihinden sonra teknik borcunuzla ne yaptınız?)
Answer:We are developing a complex feature for an e-commerce platform - an advanced search system that uses machine learning algorithms to predict and suggest user interests.
A few days before the deadline, we discover a significant issue. The machne learning model isn't training correctly, and resolving the problem would require considerable time for debugging, retraining, and validation - time we don't have.
With the deadline fast approaching, we decide to implement a workaround: instead of using the machine learning model, we develop a simpler rule-based system for the search feature. While it's not as accurate or efficient, it serves the purpose for the time being, and we manage to ship the feature on time. However, this creates technical debt in our codebase - we now have a less optimal feature that will need to be upgraded in the future.
After the deadline has passed, we don't ignore the technical debt we've accrued. We understand that while the workaround was necessary at the time, it's not a sustainable or long-term solution. We discuss the matter with the project manager and propose a plan to repay this debt. The plan includes:
Identifying the Problem: We document the details of the technical debt, its cause, and what parts of the system it affects.
Planning the Solution: We design a strategy for fixing the ML model issue. This involves debugging the problem, implementing the fix, retraining the model, and validating its results.
Scheduling: We plan out a timeline for the work, considering other project requirements and deadlines.
Implementation: We work on the issue according to the schedule, replacing the rule-based system with the intended machine learning model.
Testing and Verification: We rigorously test the new system to ensure it works as expected and improves upon the old system.
Documentation: We update all relevant documentation to reflect the changes made and document the lessons learned to prevent a similar occurrence in the future.0
8. Why do you want to leave your current job? Could you mention some general issues in your current job? Have you taken any action to mitigate/resolve those issues?
(Mevcut işinizden neden ayrılmak istiyorsunuz? Şu anki işinizle ilgili bazı genel sorunlardan bahsedebilir misiniz? Bu sorunları hafifletmek / çözmek için herhangi bir önlem aldınız mı?)
Answer:"I have had a rewarding journey at my current company and I've learned a lot, but I believe it's time for me to take on new challenges and further develop my skill set. There are a few reasons behind my decision to look for a new opportunity.
One ofthe main issues has been the limited opportunities for growth and advancement in my current role. I have a deep interest in working with emerging technologies like artificial intelligence and machine learning, but my current job does not provide the space to explore these areas. I value continuous learning and professional development, and unfortunately, it's been challenging to find those opportunities in my current role.
Another factor has been the lack of balance between work and personal life. I understand that there will be times when extra hours are necessary, especially in software development, but it has become a consistent trend rather than the exception. This has, in turn, affected my work-life balance.
I've certainly taken steps to address these issues. Regarding the growth opportunities, I've tried to incorporate learning into my personal time and have taken online courses to stay abreast with the latest technologies. However, this doesn't provide the same benefits as practical, on-the-job experience would.
In terms of the work-life balance issue, I've had open discussions with my team lead and manager about it. We've tried to distribute workload more evenly and hire additional team members to manage the workload. But the pace of the company and the resource constraints have made it difficult to bring about substantial change.
Given these circumstances, I believe it would be beneficial for my career and personal growth to explore new opportunities where I can leverage my skills and passion for emerging technologies, while also maintaining a healthier work-life balance. I'm excited about the opportunities that your company provides in both these aspects."
09. Why do you want to join us? What do you know about our company?
(Neden bize katılmak istiyorsun? Şirketimiz hakkında ne biliyorsun?)
Answer: "I'm really impressed with your company's reputation for innovation and commitment to using the latest technologies to create impactful products. As an engineer passionate about working with cutting-edge technology, it's exciting to see the breadth of projects your team takes on.
Your company's focus on artificial intelligence and machine learning, for instance, is particularly appealing to me. In my previous role, I didn't get much opportunity to explore these areas, and I'm eager to dive into this field. I've taken some online courses and done personal projects, but working with your team would provide a more in-depth, practical experience that I am looking for.
I've also read about your company's emphasis on maintaining a healthy work-life balance for your employees. This aligns with my personal values and my belief in the importance of a balanced lifestyle for productivity and creativity.
I'm also aware of your company's strong commitment to giving back to the community, which I find admirable. I appreciate that you go beyond just doing business and strive to make a positive impact in society.
Furthermore, your company culture, which encourages collaboration, continuous learning, and innovation, is very appealing. I believe this kind of environment will help me grow as a professional and contribute more effectively to the team.
In summary, I want to join your company because I believe it aligns with both my professional goals and personal values. I'm excited about the prospect of contributing to and learning from a team that's pushing boundaries in technology, all while maintaining a sustainable work-life balance and making a positive impact on the community."
10. If you have worked in many companies for short periods of time (< 2yrs), why do you switch your jobs so frequently?
(Kısa süreler için (<2 yıl) birçok şirkette çalıştıysanız, neden işlerinizi bu kadar sık değiştiriyorsunuz?)
Answer:"I appreciate your concern about the frequency of job changes in my history. I believe it's crucial to clarify that these changes were not made lightly, but were carefully considered steps in my career progression.
Early in my career, I had the opportunity to work in different start-ups, each offering unique projects and technologies. These opportunities allowed me to broaden my skill set and understand various facets of software engineering. My goal was to gain diverse experience in a relatively short amount of time, and these roles offered me the chance to do just that.
In a couple of instances, the startups I was working for underwent significant changes, such as acquisition or restructuring, which led to my decision to move on sooner than anticipated.
While it may seem that I've moved jobs frequently, each move was a strategic decision to broaden my knowledge base, increase my experience with different technologies and industries, and adapt to unforeseen circumstances.
I've always made sure to leave on good terms, having delivered significant contributions and after ensuring a smooth transition. I believe the varied experiences have made me more adaptable, versatile, and capable as a software engineer.
Having said that, I am now looking for a longer-term opportunity where I can grow deeper roots, continue to learn and contribute significantly. Your company, with its innovative projects and stability, seems like an excellent fit for this next phase of my career."
11. What is your weakness?
(Zayıf yönün nedir?)
Answer:"I think one of my weaknesses has been overcommitting myself. I am very passionate about my work and I tend to get excited about new projects or challenges, so sometimes I take on more tasks than I should. This can lead to longer hours and increased stress levels as I strive to deliver on all my commitments.
However, I've been working actively to improve in this area. I've started using project management tools and techniques to better manage my workload, and I'm becoming more conscious about the commitments I take on. I've also been learning to delegate effectively and have open discussions with my team and superiors about workload distribution and deadlines.
Recognizing this weakness has been a valuable realization for me, and while I am still a work in progress, I am committed to continuing to improve my time management and workload balancing skills. This self-improvement will not only increase my productivity but also maintain the quality of work I am known for."
12. What is your strength?
(Güçlü yönün nedir?)
Answer: I believe that my greatest strength is the ability to solve problems quickly and efficiently. I can see any given situation from multiple perspectives, which makes me uniquely qualified to complete my work even under challenging conditions. That problem solving allows me to be a better communicator. I am just as comfortable speaking to senior executives as I am junior team members. I think my ability to see all sides of an issue will make me a great asset to the team.
13. What is your current salary, or what is your salary expectation?
(Mevcut maaşınız veya maaş beklentiniz nedir?)
Answer:
14. What does your typical day look like at your current job?
(Mevcut işinizde tipik bir gününüzü nasıl geçirirsiniz?)
Answer:
15. Describe one of the biggest mistakes you have made in your job, and what did you learn?
(İşinizde yaptığınız en büyük hatalardan birini anlatın ve bu hatadan ne öğrendinizi söyleyin?)
Answer:
16. Describe a situation in which you were faced with a major obstacle in order to complete a project. How did you deal with it? What steps did you take?
(Bir projeyi tamamlamak için büyük bir engelle karşı karşıya kaldığınız bir durumu anlatın. Bununla nasıl başa çıktın? Hangi adımları attın?)
Answer:
17. How do you solve ambiguous problems?
(Belirsiz sorunları nasıl çözersiniz?)
Answer:
18. How do you see yourself in five (or ten) years? What skills do you want to learn?
(Kendinizi beş (veya on) yılda nasıl görüyorsunuz? Hangi becerileri öğrenmek istiyorsun?)
Answer: In several years, I see myself involved in architecting complex Java applications. Beyond that is too far away to think of right now.
19. Tell me about a time that you supervised/trained other engineers.
(Bana diğer mühendisleri denetlediğiniz / eğittiğiniz bir zamandan bahsedin.)
Answer:
20. Tell me about a time that you changed or improved the culture of your company or team.
(Şirketinizin veya ekibinizin kültürünü değiştirdiğiniz veya geliştirdiğiniz bir zamandan bahsedin.)
Answer:
21. Tell me about a time that you took the initiative.
(Bana inisiyatif aldığınız bir zamandan bahsedin.)
Answer:
22. Do you read any related blogs?
(İlgili herhangi bir blog okuyor musunuz?)
Answer:
23. Describe a time when you made a suggestion to improve something within the project that you were working on.
(Üzerinde çalıştığınız proje içinde bir şeyi iyileştirmek için bir öneride bulunduğunuz bir zamanı anlatın.)
Answer:
24. Give me an example of a time when you noticed a small problem before it turned into a major one. Did you take the initiative to correct it? What kind of preventive measures did you undertake?
(Küçük bir problemi büyük bir problem haline gelmeden önce fark ettiğiniz bir zamana örnek verin. Düzeltmek için inisiyatif aldınız mı? Ne tür önleyici tedbirler aldınız?)
Answer:
25. How will you adjust yourself in a fast-paced environment?
(Hızlı tempolu bir ortamda kendinizi nasıl ayarlarsınız?)
Answer:
26. What is your learning process like? How do you learn new skills?
(Öğrenme süreciniz nasıl? Yeni becerileri nasıl öğrenirsiniz?)
Answer:
27. What don’t you like in a job?
(Bir işte neyi sevmezsin?)
Answer:
28. When do you consider a project to be successful?
(Bir projenin ne zaman başarılı olduğunu düşünürsünüz?)
Answer:
29.Tell me about a time when you had to present a complex programming problem to a person that didn’t understand technical jargon. How did you ensure that the other person understood you?
30.Tell me about a time you had to work on several projects at once. How did you handle that?
Answer:
31.Describe a situation in which you have experienced a significant project change that you weren’t expecting. What was it? How did that impact you, and how did you adapt to this change? How did you remain productive through the project?
32.Tell me about a time when you had to work with a difficult person to accomplish a goal. What was the biggest challenge? How did you handle it?
Kaynaklar : CRACKING THE BEHAVIORAL INTERVIEWS FOR SOFTWARE ENGINEERS, FIRST EDITION , https://devskiller.com/45-behavioral-questions-to-use-during-non-technical-interview-with-developers/ , https://resumeperk.com/blog/behavioral-interview-questions---and-how-to-answer-them
1- Strategic Domain-Driven Design - vaadin.com - Petter Holmström - Çevirsi
Yazı Dizisinin Orjinali
Örnek DDD projesi
Serinin diğer yazıları :
2 - Tactical Domain Driven Design (Taktiksel DDD)
3 - Domain-Driven Design and the Hexagonal Architecture (DDD ve Altıgen Mimari)
1 - Strategic Domain-Driven Design
(Stratejik DDD)
Domaine Dayalı Tasarım (DDD) Eric Evans'ın konuyla ilgili kitabını 2003'te yayınladığından beri var. Birkaç yıl önce veri tutarlılığı sorunları olan bir projeye katıldığımda DDD ile haşır neşir oldum. Veritabanında tekrarlanan veriler ortaya çıktı, bazı bilgiler hiç kaydedilmedi ve her yerde ve her zaman iyimser kilitleme hatalarıyla karşılaşabileceğimi gördüm. Taktiksel DDD tasarımın yapı taşlarını kullanarak bunu çözmeyi başardık.Domain(Etki Alanı) nedir?
- belirli bir faaliyet veya bilgi alanı ...
Subdomains (Alt alanlar)
- Çekirdek alanlar (Core Domains)
- Destekleyen alt alanlar (Supporting Subdomains)
- Genel alt alanlar (Generic Subdomains)
Misal
- Hasta tıbbi kayıtlarını yönetmek için Hasta Kayıtları(Patient Records) (kişisel bilgiler, tıbbi geçmiş, vb.).
- Laboratuvar testleri sipariş etmek ve test sonuçlarını yönetmek için laboratuar(Lab).
- Randevuları planlamak için planlama(Scheduling ).
- Hasta kayıtlarına (farklı belgeler, röntgen resimleri, taranmış kağıt belgeler gibi) eklenmiş dosyaları depolamak ve yönetmek için Dosya Arşivi (File Archive).
- Doğru kişilerin doğru bilgilere erişimini sağlamak için Kimlik Yönetimi (Identity Management).
Sorunlardan Çözümlere
The Ubiquitous Language (Ortak bir dil).
Introducing Bounded Contexts(Sınırlı Bağlamlara Giriş)
Misal
- Hastanın kişisel bilgilerini yönetmek için kişisel bilgiler (Personal Information) (isim, adres, mali bilgiler, tıbbi geçmişi, vb.).
- Sisteme yeni hastaları tanıtmak için ilk katılım (Onboarding).
- Doktorların hastayı muayene ederken ve tedavi ederken kullandıkları Tıbbi Muayeneler (Medical Exams).
- Fizyoterapistlerin hastayı muayene ederken ve tedavi ederken kullandıkları fizyoterapi(Physiotherapy).
Bağlamlar Arasındaki İlişkiler
Context Maps and Integration Patterns(Bağlam Haritaları ve Entegrasyon Modelleri)
- Bağlam sınırları nerede?
- Bağlamlar teknik olarak nasıl iletişim kuracak?
- Bağlamların domain modelleri arasında nasıl harita oluşturacağız (yani yeknesak bir dilden diğerine nasıl çeviri yapıyoruz)?
- Yukarı yönde meydana gelen istenmeyen veya sorunlu değişikliklere karşı nasıl korunacağız?
- Aşağı akış bağlamlarında sorun yaratmaktan nasıl kaçınacağız?
Partnership(Ortaklık)
Shared Kernel (Paylaşılan Çekirdek)
Customer-Supplier (Müşteri - Satıcı)
Conformist (Uyumlu Kimse)
Anticorruption Layer (Adaptosyon Katmanı)
Open Host Service
Published Language (Yayın Dili)
Separate Ways (Ayrı Roller)
Stratejik Domaine Dayalı Tasarım Neden Önemlidir?
- Sınırlar getirir. Kapsam sünmesi (Scope creep), tüm hobi projelerimde değişmeyen bir faktördür. Sonunda, üzerinde çalışmak eğlenceden daha kapsamlı hale gelir veya tek başına bitirmek tamamen gerçekçi olmaz. Müşteri projeleri üzerinde çalışırken, bir şeyleri aşırı düşünerek veya aşırı mühendislik yaparak teknik kapsamın sünmesine neden olmamak için çok çalışmam gerekir. Sınırlar - nerede olurlarsa olsunlar - projeyi daha küçük parçalara bölmeme ve doğru zamanda doğru olanlara odaklanmama yardımcı oluyor.
- Her durumda çalışan bir süper model bulmamı gerektirmiyor. Stratejik DDD gerçek dünyada, genellikle az çok açıkça tanımlanmış bağlamlarda birçok küçük modelin olduğunu kabul eder. Bu modelleri kırmak yerine kucaklar.
- Sisteminizin getireceği değeri ve en büyük değeri elde etmek için çabalarınızın çoğunu nereye koymanız gerektiğini düşünmenize yardımcı olur. Doğru bir şekilde tanımlamanın ve ardından core domaine odaklanmanın büyük bir fark yaratacağı projelerden kişisel deneyime sahibim. Ne yazık ki, stratejik DDD'yi henüz duymamıştım ve hem zaman hem de para israf ettim.
Softtech 2020 Teknoloji Raporu
Softtech 2020 Teknoloji Raporu, 2020 yılında teknolojik yenilikleri detaylı halde raporlayan güzel bir çalışma olmuş.
Bahsedilen rapora buradan ulaşabilirsiniz.
NoSQL - İlişkisel Veritabanları (RDMS) Karşılaştırması - mongodb.com çevirisi
NoSQL - İlişkisel Veritabanları (RDMS) Karşılaştırması
İlişkisel Veritabanlarının (RDBMS) ve NoSQL'in Tarihçesi
1-Veri Modelleri ve Şema
2-Veri yapısı
3-Ölçekleme
4-Geliştirme Modeli
NoSQL veritabanları daha çok açık kaynak topluluğunun bir parçası olma eğilimindedir. İlişkisel veritabanları, yazılımlarının kullanımı için kullanılan lisanslama ücretleri ile tipik olarak kapalı bir kaynaktır.Genel NoSQL ve İlişkisel Veritabanı (diğer adıyla SQL) Soruları
SQL ve NoSQL arasındaki farklar
NoSQL Veritabanlarının Faydaları Nelerdir?
NoSQL Veritabanlarının Dezavantajları Nelerdir?
Monolithic mimarinin nispeten küçük uygulamalarda avantajları ve uygulama büyüdükçe yaşattığı zorluklar
Bunlar:
1-) Kolay geliştirme : IDE'ler ve diğer geliştirici araçları sadece bir uygulamayı geliştirmek için focus oluyorlar.
2-) Uygulama üzerinde radikal değişiklikler yapabilmek : Radikal bir şekilde kodu, database şemasını kolayca değiştirip build ve deploy yapmanız microservice'e göre çok daha kolay.
3-)Test edilmesi kolay : Geliştiriciler, uygulamayı başlatan, REST API'sini çağıran ve Selenium ile kullanıcı arayüzünü test eden uçtan uca testleri rahatlıkla yazabilirler.
4-) Dağıtımı kolay : Bir geliştiricinin tek yapması gereken, WAR dosyasını Tomcat'in yüklü olduğu bir sunucuya kopyalamaktır.
5-) Ölçeklendirmesi kolay : Uygulamanın birden fazla örneğini bir yük dengeleyicinin arkasında çalıştırabilirsiniz.
Ancak zamanla geliştirme, test, dağıtım ve ölçeklendirme çok daha zor hale gelir. Eğer uygulamanın karmaşıklığı önceden ileride artacağı tahmin edilebiliyorsa microservice mimari ile başlanmadılır. Küçük ve karmaşık olmayan uygulamalar monolotich mimariye daha uygundur.
Monolotich mimari ile yazılan bir uygulama düşünelim. Herbir sprint'de uygulamaya yeni özellikler eklenir ve uygulama code base'i giderek büyür. Buna bağlı olarak yazılım ekibi de giderek kalabalıklaşır. Uygulamayı yönetmek giderek zorlaşmaya başlar. Fonksiyonel olarak ayrılmış Agile takımlar ortaya çıkar. Agile yöntemler kullanmak giderek zor hale gelir.
Uyguluma giderek karmaşıklaştıkca ve büyüdükçe monolotich mimaride yaşanan zorluklar :
1-) Karmaşıklık geliştiricileri korkutur hale gelir : Uygulama çok karmaşık hale geldiğinden hataları düzeltmek ve yeni özellikleri doğru şekilde uygulamak zor ve zaman alıcı hale gelir. Son teslim tarihleri genellikle kaçırılır.
2-) Geliştirme yavaşlar : Büyük uygulama, geliştiricinin IDE'sini aşırı yükler ve yavaşlatır. Uygulamayı build etmek uzun zaman alır. Dahası, çok büyük olduğu için, uygulamanın başlatılması uzun zaman alır. Sonuç olarak, edit-build-run-test döngüsü uzun zaman alır ve bu da üretkenliği kötü etkiler.
3-) Commit'den deployment'a giden uzun ve yorucu yol : Uygulamayla ilgili bir diğer sorun da değişikliklerin üretime aktarılmasının uzun ve acı verici bir süreç olmasıdır. Ekip genellikle ayda bir kez, genellikle Cuma veya Cumartesi gecesi geç saatlerde üretim güncellemelerini dağıtır. Ve continuous deployment benimsemek imkansız gibi görünür. Feature branch'lerinin merge işlemi saatler alabilir ve production'a geçmenin bu kadar uzun sürmesinin bir başka nedeni de testin uzun sürmesidir. Kod tabanı çok karmaşık olduğundan ve bir değişikliğin etkisi iyi anlaşılmadığından, geliştiriciler ve Sürekli Entegrasyon (CI) sunucusu tüm test paketini çalıştırmalıdır.
4-) Ölçeklendirme zor : Bunun nedeni, farklı uygulama modüllerinin çakışan kaynak gereksinimleri olmasıdır. Örneğin uygulamada bazı modüller , çok fazla belleğe sahip sunucularda ideal olarak deploy edilen büyük bellek veritabanında saklanırken, görüntü işleme modülü CPU yoğun bir işlemdir ve en iyi CPU'lara sahip sunucularda en iyi şekilde çalışır. Bu modüller aynı uygulamanın bir parçası olduğundan, sunucu yapılandırmasında bu aşamada ödünler verilmesi gerekir.
5-) Reliable bir monolitich sunmak zor : Reliable olmamasının bir nedeni, büyük boyutu nedeniyle uygulamayı kapsamlı bir şekilde test etmenin zor olmasıdır. Bu test edilebilirlik eksikliği, hataların productiona geçmesi anlamına gelir. Daha da kötüsü, tüm modüller aynı işlem içinde çalıştığından uygulamada hata izolasyonu yoktur. Çoğu zaman, bir modüldeki bir hata - örneğin bir bellek sızıntısı - uygulamanın tüm modullerini tek tek çökertir.
6-) Giderek kullanılmayan teknolojiye mahkum kalmak : Mimarinin giderek modası geçmiş bir teknoloji yığını kullanmaya zorlamasıdır. Monolitik mimari, yeni frameworklerin ve dillerin benimsenmesini zorlaştırmaktadır. Tüm monolitik uygulamayı yeniden yazmak son derece pahalı ve riskli olacaktır, böylece yeni ve muhtemelen daha iyi bir teknoloji kullanamayacaktır. Sonuç olarak, geliştiriciler projenin başlangıcında yaptıkları teknoloji seçimlerine bağlı kalırlar.
2- Tactical Domain-Driven Design (Java Kod Örnekleri İle) - vaadin.com - Petter Holmström - Çevirsi
"Bu makale dizisinde, Domain Driven Desgin (etki alnına dayalı tasarım)'ın ne olduğunu ve projenize - veya bir kısmının - projelerinize nasıl uygulanacağını öğreneceksiniz." diyor Petter Holmström. Ben de elimden geldiğince bu yazı dizisini Türkçe'ye çevirmeye çalışacağım. Umarım İngilizce okumada zorluk çeken arkadaşlar için yararlı olur.
Yazı Dizisinin Orjinali
Örnek DDD projesi
Serinin diğer yazıları :
1 - Strategic Domain Driven Design (Stratejik DDD)
3 - Domain-Driven Design and the Hexagonal Architecture (DDD ve Altıgen Mimari)
2 - Tactical Domain-Driven Design
(Taktiksel DDD)Taktiksel DDD'deki en önemli kavramlardan biri değer nesnesidir. Bu aynı zamanda DDD olmayan projelerde en çok kullandığım DDD yapı taşıdır ve umarım bunu okuduktan sonra siz de kullanırsınız.
- Parasal değerler için bir BigDecimal kullanmak yerine, BigDecimal'ı saran bir Money değeri nesnesi kullanın. Birden fazla para birimi ile uğraşıyorsanız, bir Currency değer nesnesi de oluşturarak, Money nesnenizin bir BigDecimal-Currency çiftini sarmasını isteyebilirsiniz.
- Telefon numaraları ve e-posta adresleri için String kullanmak yerine, Stringleri saran PhoneNumber ve EmailAddress değer nesnelerini kullanın.
public class Money implements Serializable, Comparable<Money> {
private final BigDecimal amount;
private final Currency currency; // Currency is an enum or another value object
public Money(BigDecimal amount, Currency currency) {
this.currency = Objects.requireNonNull(currency);
this.amount = Objects.requireNonNull(amount).setScale(currency.getScale(), currency.getRoundingMode());
}
public Money add(Money other) {
assertSameCurrency(other);
return new Money(amount.add(other.amount), currency);
}
public Money subtract(Money other) {
assertSameCurrency(other);
return new Money(amount.subtract(other.amount), currency);
}
private void assertSameCurrency(Money other) {
if (!other.currency.equals(this.currency)) {
throw new IllegalArgumentException("Money objects must have the same currency");
}
}
public boolean equals(Object o) {
// Check that the currency and amount are the same
}
public int hashCode() {
// Calculate hash code based on currency and amount
}
public int compareTo(Money other) {
// Compare based on currency and amount
}
}
Java'da bir StreetAddress değer nesnesi ve karşılık gelen builder şöyle görünebilir (kod test edilmemiştir ve netlik için bazı yöntem uygulamaları atlanmıştır):
StreetAddress.java
public class StreetAddress implements Serializable, Comparable<StreetAddress> {
private final String streetAddress;
private final PostalCode postalCode; // PostalCode is another value object
private final String city;
private final Country country; // Country is an enum
public StreetAddress(String streetAddress, PostalCode postalCode, String city, Country country) {
// Verify that required parameters are not null
// Assign the parameter values to their corresponding fields
}
// Getters and possible business logic methods omitted
public boolean equals(Object o) {
// Check that the fields are equal
}
public int hashCode() {
// Calculate hash code based on all fields
}
public int compareTo(StreetAddress other) {
// Compare however you want
}
public static class Builder {
private String streetAddress;
private PostalCode postalCode;
private String city;
private Country country;
public Builder() { // For creating new StreetAddresses
}
public Builder(StreetAddress original) { // For "modifying" existing StreetAddresses
streetAddress = original.streetAddress;
postalCode = original.postalCode;
city = original.city;
country = original.country;
}
public Builder withStreetAddress(String streetAddress) {
this.streetAddress = streetAddress;
return this;
}
// The rest of the 'with...' methods omitted
public StreetAddress build() {
return new StreetAddress(streetAddress, postalCode, city, country);
}
}
}
Entities
Entity or Value Object?
(Varlık veta Değer Nesnesi)
public class Person {
private final PersonId personId;
private final EventLog changeLog;
private PersonName name;
private LocalDate birthDate;
private StreetAddress address;
private EmailAddress email;
private PhoneNumber phoneNumber;
public Person(PersonId personId, PersonName name) {
this.personId = Objects.requireNonNull(personId);
this.changeLog = new EventLog();
changeName(name, "initial name");
}
public void changeName(PersonName name, String reason) {
Objects.requireNonNull(name);
this.name = name;
this.changeLog.register(new NameChangeEvent(name), reason);
}
public Stream getNameHistory() {
return this.changeLog.eventsOfType(NameChangeEvent.class).map(NameChangeEvent::getNewName);
}
// Other getters omitted
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (o == null || o.getClass() != getClass()) {
return false;
}
return personId.equals(((Person) o).personId);
}
public int hashCode() {
return personId.hashCode();
}
}
Bu örnekte dikkat edilmesi gereken bazı noktalar:
- Varlık ID'si için bir değer nesnesi - PersonId - kullanılır. Bir UUID, bir String veya bir Long kullanabilirdik, ancak bir değer nesnesi hemen bunun belirli bir Person'u tanımlayan bir ID olduğunu söyler.
- Varlık kimliğine ek olarak, bu varlık birçok başka değer nesnesi de kullanır: PersonName, LocalDate (evet, bu standart Java API'sinin bir parçası olmasına rağmen bir değer nesnesidir), StreetAddress, EmailAddress ve PhoneNumber.
- Adı değiştirmek için bir setter kullanmak yerine, adın değiştirilme nedeninin yanı sıra, değişikliği bir olay günlüğünde de saklayan bir business metodu kullanırız.
- İsim değişikliklerinin geçmişini almak için bir getter var.
- equals ve hashCode yalnızca varlık ID'sini kontrol eder.
- Ana görünüm, belki de filtreleme ve sıralama ile varlıkları arayabileceğiniz (alma(retrieve)) bir grid'den oluşur.
- Ana görünümde, yeni varlıklar oluşturmak için bir button vardır. Buttona tıklamak boş bir form getirir ve form gönderildiğinde, yeni varlık gridde görünür (oluştur (create)).
- Ana görünümde, seçilen objeyi düzenlemek için bir button bulunur. Düğmeye tıklandığında varlık verilerini içeren bir form açılır. Form gönderildiğinde, varlık yeni bilgilerle güncellenir (güncelleme(update)).
- Ana görünümde, seçilen varlığı silmek için bir button bulunur. Buttona tıklamak varlığı gridden siler (sil(delete)).
- Yönetici, çalışanın sistemdeki kaydını arar.
- Yönetici 'İş Sözleşmesini Sonlandır' eylemini seçer.
- Sistem sonlandırma tarihini ve nedenini sorar.
- Yönetici gerekli bilgileri girer ve 'Sözleşmeyi Sonlandır' seçeneğini tıklatır.
- Sistem, çalışan kayıtlarını otomatik olarak günceller, çalışanın kullanıcı bilgilerini ve elektronik ofis anahtarını iptal eder ve bordro sistemine bir bildirim gönderir.
- Yönetici, çalışanın sistemdeki kaydını arar.
- Yönetici, 'Sözleşme feshedildi' onay kutusuna bir onay işareti koyar ve fesih tarihini girer, ardından 'Kaydet'i tıklatır.
- Yönetici, kullanıcı yönetim sistemine giriş yapar, kullanıcının hesabına bakar, 'Devre dışı' onay kutusuna bir onay işareti ekler ve 'Kaydet'i tıklar.
- Yönetici, ofis anahtarı yönetim sisteminde oturum açar, kullanıcının anahtarını arar, 'Devre dışı' onay kutusuna bir onay işareti koyar ve 'Kaydet'i tıklar.
- Yönetici bordro departmanına çalışanın işten ayrıldığını bildiren bir e-posta gönderir.
- Bir agregaya dışarıdan sadece köküne referans verilebilir. Agrega dışındaki nesneler, agrega içindeki herhangi bir başka varlığı referans gösteremez.
- Agrega kökü, agrega içinde business değişmezlerin(invariants) uygulanmasından sorumludur ve agreganın her zaman tutarlı bir durumda olmasını sağlar.
- Varlığa uygulamadan nasıl erişelecek?
- Varlık ID ile veya bir tür arama yoluyla aranacaksa, büyük olasılıkla bir aggregate köküdür.
- Diğer aggregate'ler varlığı referans alması gerekecek mi?
- Eğer varlığa diğer aggregatelerden referans verilecekse, kesinlikle bir aggregate köküdür.
- Uygulamada varlık nasıl değiştirilecek(modifiye edilecek)?
- Bağımsız olarak modifiye edilebilirse, muhtemelen bir aggregate köküdür.
- Başka bir varlıkta değişiklik yapılmadan değiştirilemezse, büyük olasılıkla local bir varlıktır.
- Tüm durum değiştirme işlemleri aggregate kök üzerinden gerçekleştirilir.
- Local varlıklar üzerinde durum değiştirme işlemlerine izin verilir, ancak her değiştiğinde aggregate köke bildirir.
Doğrudan başka bir aggreagete referans göstermek yerine, agrega kökü ID'sini saran bir değer nesnesi oluşturun ve bunu referans olarak kullanın. Bu, bir agregate'in durumunu yanlışlıkla bir diğerinin içinden değiştiremeyeceğiniz için toplam tutarlılık sınırlarının korunmasını kolaylaştırır. Ayrıca, toplu bir nesne alındığında derin nesne ağaçlarının veri deposundan alınmasını önler.
public class Invoice extends AggregateRoot {
private CustomerId customerId;
// All the other methods and fields omitted
public void copyCustomerInformationToInvoice(CustomerRepository repository) {
Customer customer = repository.findById(customerId);
setCustomerName(customer.getName());
setCustomerAddress(customer.getAddress());
// etc.
}
}
YÖNERGE 3: TRNASACTION BAŞINA BİR AGREGA DEĞİŞTİRİN
İşlemlerinizi, tek bir transaction içinde yalnızca bir agregada değişiklik yapacak şekilde tasarlamaya çalışın. Birden çok agrega içeren işlemler için domain eventlerini ve nihai tutarlılığı kullanın (bunun hakkında daha fazla konuşacağız). Bu, istenmeyen yan etkileri önler ve gerekirse sistemin gelecekte distribute edilmesini kolaylaştırır. Ayrıca, document veritabanlarının transaction desteği olmadan kullanılmasını da kolaylaştırır.
Her durumda, bir agreganın durumunu doğrudan başka bir agreganın içinden değiştirmekten kaçınmalısınız.
Domain eventlerini ele aldığımızda bununla ilgili daha fazla tartışacağız.
YÖNERGE 4: OPTIMISTIC LOCKING KULLANIN
Agregaların temel özelliği, business değişmezlerini zorlamak ve veri tutarlılığını her zaman sağlamaktır. Agrega, çakışan veri depolama güncellemeleri nedeniyle bozulursa, bu durum boşuna olur. Bu nedenle, agregaları kaydederken veri kaybını önlemek için optimistic locking kullanmalısınız.
Optimistic locking'in pestimistic locking'e tercih edilmesinin nedeni, eğer persistence framework optimistic locking'i desteklemiyorsa bunu kendinizin gerçekleştirebilmesi ve distribute edilmesinin ve ölçeklendirmenin kolay olmasıdır.
Küçük agregalar (ve dolayısıyla küçük transactionlar) de çatışma riskini azalttığı için ilk yönergeye bağlı kalmak da bu konuda yardımcı olacaktır.
Agregalar, Değişmezler, Kullanıcı Arayüzü Bağlama ve Doğrulama
Bazılarınız büyük olasılıkla şimdi agregaların ve zorlayıcı iş değişmezlerinin kullanıcı arayüzleriyle birlikte nasıl çalıştıklarını ve daha spesifik olarak bağlayıcı oluşturduklarını merak ediyorsunuz. Değişmezler her zaman uygulanacaksa ve bir agreganın her zaman tutarlı bir durumda olması gerekiyorsa, kullanıcı formları doldururken ne yaparsınız? Ayrıca, setterlar yoksa, form alanlarını agregalara nasıl bağlarsınız?
Bununla baş etmenin birçok yolu vardır. En basit çözüm, agrega kaydedilene kadar değişmez zorlamayı ertelemek, tüm özellikler için setter'lar eklemek ve entityleri doğrudan forma bağlamaktır. Ben şahsen bu yaklaşımı sevmiyorum çünkü bu DDD olmaktan çok veri güdümlü olduğuna inanıyorum. Entitylerin, business logic'i bir hizmet katmanında (veya daha kötüsü, kullanıcı arayüzünde) sonuçlanacak şekilde anemik veri sahiplerine ayrılma riski yüksektir.
bkz : anemic domain model
Bunun yerine, diğer iki yaklaşımı tercih ediyorum. Birincisi, formları ve içeriklerini kendi domain modeli kavramlarına modellemektir. Gerçek dünyada, bir şey için başvurursanız, genellikle bir başvuru formu doldurmanız ve göndermeniz gerekir. Uygulama daha sonra işlenir ve gerekli tüm bilgiler sağlandıktan ve kurallara uyduğunuzda, uygulama çalışır ve başvurduğunuz her şeyi alırsınız. Domain modelinde bu işlemi taklit edebilirsiniz. Örneğin, Üyelik agrega kökü varsa, Üyelik oluşturmak için gereken tüm bilgileri toplamak için kullanılan bir MembershipApplication agrega köküne de sahip olabilirsiniz. Uygulama nesnesi daha sonra üyelik nesnesi oluşturulurken girdi olarak kullanılabilir.
İkinci yaklaşım birincinin bir varyantıdır ve essence pattern'idir. Düzenlemeniz gereken her varlık veya değer nesnesi için aynı bilgileri içeren değiştirilebilir(mutable) bir essence nesne oluşturun. Bu essence nesne daha sonra forma bağlanır. Essence nesnesi gerekli tüm bilgileri içerdikten sonra, gerçek varlıklar veya değer nesneleri oluşturmak için kullanılabilir. İlk yaklaşımın farkı, essence nesnelerin domain modelinin bir parçası olmaması, sadece gerçek domain nesneleriyle etkileşimi kolaylaştırmak için var olan teknik yapılardır. Pratikte, essence pattern böyle bir şeye benzeyebilir:
public class Person extends AggregateRoot {
private final DateOfBirth dateOfBirth;
// Rest of the fields omitted
public Person(String firstName, String lastName, LocalDate dateOfBirth) {
setDateOfBirth(dateOfBirth);
// Populate the rest of the fields
}
public Person(Person.Essence essence) {
setDateOfBirth(essence.getDateOfBirth());
// Populate the rest of the fields
}
private void setDateOfBirth(LocalDate dateOfBirth) {
this.dateOfBirth = Objects.requireNonNull(dateOfBirth, "dateOfBirth must not be null");
}
@Data // Lombok annotation to automatically generate getters and setters
public static class Essence {
private String firstName;
private String lastName;
private LocalDate dateOfBirth;
private String streetAddress;
private String postalCode;
private String city;
private Country country;
public Person createPerson() {
validate();
return new Person(this);
}
private void validate() {
// Make sure all necessary information has been entered, throw an exception if not
}
}
}
İsterseniz, bu kalıba daha aşina iseniz essence'i bir builder ile değiştirebilirsiniz. Sonuç aynı olurdu.
KOD ÖRNEKLERİ
Aşağıda, local ID'ye sahip bir agreaga kök (Order) ve bir local varlık (OrderItem) örneği verilmiştir (kod test edilmemiştir ve netlik için bazı metod uygulamaları atlanmıştır):
Order.java
public class Order extends AggregateRoot { // ID type passed in as generic parameter
private CustomerId customer;
private String shippingName;
private PostalAddress shippingAddress;
private String billingName;
private PostalAddress billingAddress;
private Money total;
private Long nextFreeItemId;
private List items = new ArrayList<>();
public Order(Customer customer) {
super(OrderId.createRandomUnique());
Objects.requireNonNull(customer);
// These setters are private and make sure the passed in parameters are valid:
setCustomer(customer.getId());
setShippingName(customer.getName());
setShippingAddress(customer.getAddress());
setBillingName(customer.getName());
setBillingAddress(customer.getAddress());
nextFreeItemId = 1L;
recalculateTotals();
}
public void changeShippingAddress(String name, PostalAddress address) {
setShippingName(name);
setShippingAddress(address);
}
public void changeBillingAddress(String name, PostalAddress address) {
setBillingName(name);
setBillingAddress(address);
}
private Long getNextFreeItemId() {
return nextFreeItemId++;
}
void recalculateTotals() { // Package visibility to make the method accessible from OrderItem
this.total = items.stream().map(OrderItem::getSubTotal).reduce(Money.ZERO, Money::add);
}
public OrderItem addItem(Product product) {
OrderItem item = new OrderItem(getNextFreeItemId(), this);
item.setProductId(product.getId());
item.setDescription(product.getName());
this.items.add(item);
return item;
}
// Getters, private setters and other methods omitted
}
public class OrderItem extends LocalEntity { // ID type passed in as generic parameter
private Order order;
private ProductId product;
private String description;
private int quantity;
private Money price;
private Money subTotal;
OrderItem(Long id, Order order) {
super(id);
this.order = Objects.requireNonNull(order);
this.quantity = 0;
this.price = Money.ZERO;
recalculateSubTotal();
}
private void recalculateSubTotal() {
Money oldSubTotal = this.subTotal;
this.subTotal = price.multiply(quantity);
if (oldSubTotal != null && !oldSubTotal.equals(this.subTotal)) {
this.order.recalculateTotals(); // Invoke aggregate root to enforce invariants
}
}
public void setQuantity(int quantity) {
if (quantity < 0) {
throw new IllegalArgumentException("Quantity cannot be negative");
}
this.quantity = quantity;
recalculateSubTotal();
}
public void setPrice(Money price) {
Objects.requireNonNull(price, "price must not be null");
this.price = price;
recalculateSubTotal();
}
// Getters and other setters omitted
}
Domain Events
(Domain Olayları)
Şimdiye kadar yalnızca domain modelindeki "şeylere" baktık. Bununla birlikte, bunlar sadece modelin herhangi bir anda bulunduğu statik durumu tanımlamak için kullanılabilir. Birçok iş modelinde, gerçekleşen şeyleri tanımlamanız ve modelin durumunu değiştirebilmeniz gerekir. Bunun için domain eventlerini kullanabilirsiniz.
Domain eventleri Evans'ın DDD hakkındaki kitabına dahil edilmedi. Araç kutusuna daha sonra eklendi ve Vernon’un kitabına dahil edildi.
Bir domain eventi, domain modelinde, sistemin diğer bölümlerini ilgilendirebilecek herhangi bir şeydir. Domain eventleri, kaba taneli(coarse-grained) (ör. Belirli bir agrega kök oluşturulur veya bir işlem başlatılır) veya ince taneli(fine-grained) (ör. Belirli bir agrega kökün belirli bir özniteliği değiştirilir) olabilir.
- Domain eventleri genellikle aşağıdaki özelliklere sahiptir:
- Değişmezler(Immutable) (sonuçta geçmişi değiştiremezsiniz).
- Söz konusu olay gerçekleştiğinde zaman damgası(timestamp) vardır.
- Bir etkinliği diğerinden ayırt etmeye yardımcı olan benzersiz bir kimliğe(ID) sahip olabilirler.Bu, olayın türüne ve olayın nasıl dağıtıldığına bağlıdır.
- Agrega kökler veya domain servisleri(daha sonra hakkında daha fazla bilgi vereceğiz) tarafından yayınlanır.
Bir domain eventi yayınlandıktan sonra, bir veya daha fazla domain eventi dinleyicisi tarafından alınabilir ve bu da ek işleme ve yeni domain eventlerini tetikleyebilir. Yayıncı, eventin ne olduğunun farkında değildir ve dinleyici de yayıncıyı etkileyemez (diğer bir deyişle, domain eventlerini yayınlamak yayıncının bakış açısından yan etki içermemelidir). Bu nedenle, domain event dinleyicilerinin eventi yayımlayan ile aynı işlem içinde çalıştırılmaması önerilir.
Tasarım açısından bakıldığında, domain eventlerinin en büyük avantajı sistemi genişletilebilir hale getirmeleridir. Mevcut kodu değiştirmek zorunda kalmadan yeni iş mantığını tetiklemek için ihtiyaç duyduğunuz sayıda domain event dinleyicisi ekleyebilirsiniz. Bu doğal olarak doğru eventlerin ilk etapta yayınlandığını varsayar. Bazı eventlerin önceden farkında olabilirsiniz, ancak diğerleri kendilerini yolda daha fazla gösterecektir. Elbette, ne tür eventlere ihtiyaç duyulacağını tahmin etmeye ve bunları modelinize eklemeye çalışabilirsiniz, ancak daha sonra hiçbir yerde kullanılmayan domain eventleriyle sistemi tıkama riski de vardır. Daha iyi bir yaklaşım, domain eventlerini yayınlamayı mümkün olduğunca kolaylaştırmak ve daha sonra bunlara ihtiyacınız olduğunu fark ettiğinizde eksik eventleri eklemektir.
Event Kaynaklarına İlişkin Not
Event kaynağı, bir sistemin durumunun düzenli bir event günlüğü olarak devam ettiği bir tasarım modelidir. Her biri sistemin durumunu değiştirir ve mevcut durum, event günlüğünü baştan sona tekrar oynatarak herhangi bir zamanda hesaplanabilir. Bu örüntü, özellikle tarihin mevcut durum kadar önemli (hatta daha önemli) olduğu finansal defterler veya tıbbi kayıtlar gibi uygulamalarda kullanışlıdır.
Deneyimlerime göre, tipik bir iş sisteminin çoğu bölümü event kaynağı gerektirmez, ancak bazı bölümleri gerektirir. Tüm sistemi event kaynağı oluşturmayı bir kalıcılık modeli olarak kullanmaya zorlamak, bence, aşırıya kaçmak olacaktır. Ancak, domain eventlerinin gerektiğinde event kaynağı oluşturmak için kullanılabileceğini buldum. Uygulamada, bu, modelin durumunu değiştiren her işlemin, bazı event günlüğünde depolanan bir domain eventi de yayınlayacağı anlamına gelir. Bunun teknik olarak nasıl yapılacağı bu yazının kapsamı dışındadır.
Domain Eventlerini Dağıtma
Domain eventleri yalnızca bunları dinleyicilere dağıtmanın güvenilir bir yoluna sahipseniz kullanılabilir. Bir monolitin içinde, standart observer desenini kullanarak bellek içi dağılımı işleyebilirsiniz. Ancak, bu durumda bile, event yayıncılarını ayrı transactionlarda çalıştırmanın en iyi yöntemlerini izlerseniz daha karmaşık bir şeye ihtiyacınız olabilir. Event dinleyicilerinden biri başarısız olursa ve event yeniden gönderilirmesi gerekirse ne olur?
Vernon, hem uzaktan hem de local olarak çalışan eventleri dağıtmanın iki farklı yolunu sunar. Detaylar için kitabını okumanızı tavsiye ederim ama buradaki seçeneklerin kısa bir özetini vereceğim.
MESAJ KUYRUĞU İLE DAĞITIM
Bu çözüm, AMQP veya JMS gibi harici bir mesajlaşma çözümü (MQ) gerektirir. Çözüm, publish-subscribe modelini ve garantili teslimatı desteklemelidir. Bir domain eventi yayınlandığında, publisher eventi MQ'ya gönderir. Domain event dinleyicileri MQ'ya abone olur ve hemen bilgilendirilir.
Bu modelin avantajları, hızlı, uygulanması oldukça kolay ve mevcut denenmiş ve gerçek mesajlaşma çözümlerine güvenmesidir. Dezavantajları, MQ çözümünü kurmanız ve sürdürmeniz gerektiğidir ve yeni bir tüketici abone olursa geçmiş olayları almanın bir yolu yoktur.
EVENT LOG İLE DAĞILIMI
Bu çözüm ek bileşen gerektirmez, ancak kodlama gerektirir. Bir domain eventi yayınlandığında, log'a eklenir. Domain event dinleyicileri, yeni eventleri kontrol etmek için bu logu düzenli olarak yoklar. Ayrıca, her seferinde tüm logu gözden geçirmek zorunda kalmamak için zaten hangi eventleri işlediklerini takip ederler.
Bu modelin avantajları, herhangi bir ek bileşen gerektirmemesi ve yeni event dinleyicileri için tekrarlanabilen eksiksiz bir olay geçmişi içermesidir. Dezavantajı, uygulanması için biraz çalışma gerektirmesi ve bir dinleyici tarafından yayınlanan ve alınan bir event arasındaki gecikmenin en çok yoklama aralığında olmasıdır.
Nihai Tutarlılık Üzerine Bir Not
Veri tutarlılığı, dağıtılmış sistemlerde veya aynı mantıksal transactionda birden fazla veri deposunun yer aldığı bir sorundur. Gelişmiş uygulama sunucuları, bu sorunu çözmek için kullanılabilen dağıtılmış işlemleri destekler, ancak özel yazılım gerektirirler ve yapılandırılması ve bakımı karmaşık işler olabilir. Güçlü tutarlılık mutlak bir gereklilikse, dağıtılmış transactionları kullanmaktan başka seçeneğiniz yoktur, ancak birçok durumda güçlü tutarlılığın aslında bir iş açısından o kadar da önemli olmadığı ortaya çıkabilir. Biz sadece tek bir ACID transactionunda tek bir veritabanı ile konuşan tek bir uygulamamız olduğu zamanlardan beri güçlü tutarlılık açısından düşünmeye alışkınız.
Güçlü tutarlılığın alternatifi nihai tutarlılıktır. Bu, uygulamadaki verilerin nihayetinde tutarlı hale geleceği anlamına gelir, ancak sistemin tüm bölümlerinin birbiriyle senkronize olmadığı ve mükemmel derecede iyi olduğu zamanlar olacaktır. Nihai tutarlılık için bir uygulama tasarlamak farklı bir düşünme yöntemi gerektirir, ancak bunun karşılığında, yalnızca güçlü tutarlılık gerektiren bir sistemden daha esnek ve ölçeklenebilir bir sistem elde edilebilir.
DDD bir sistemde domain eventleri, nihai tutarlılığa ulaşmanın mükemmel bir yoludur. Başka bir modülde veya sistemde bir şey olduğunda kendini güncellemesi gereken herhangi bir sistem veya modül, o sistemden gelen domain eventlerine abone olabilir:
Aşağıda, sipariş gönderildiğinde bir domain eventi (OrderShipped) yayınlayan bir agrega kök (Order) örneği verilmiştir. Bir domain dinleyicisi (InvoiceCreator) eventi alır ve ayrı bir işlemde yeni bir fatura oluşturur. Agrega kök kaydedildiğinde kayıtlı tüm eventleri yayınlayan bir mekanizma olduğu varsayılmaktadır (kod test edilmemiştir ve netlik için bazı yöntem uygulamaları atlanmıştır):
OrderShipped.java
public class OrderShipped implements DomainEvent {
private final OrderId order;
private final Instant occurredOn;
public OrderShipped(OrderId order, Instant occurredOn) {
this.order = order;
this.occurredOn = occurredOn;
}
// Getters omitted
}
public class Order extends AggregateRoot{ // Other methods omitted public void ship() { // Do some business logic registerEvent(new OrderShipped(this.getId(), Instant.now())); } }
public class InvoiceCreator {
final OrderRepository orderRepository;
final InvoiceRepository invoiceRepository;
// Constructor omitted
@DomainEventListener
@Transactional
public void onOrderShipped(OrderShipped event) {
var order = orderRepository.find(event.getOrderId());
var invoice = invoiceFactory.createInvoiceFor(order);
invoiceRepository.save(invoice);
}
}
Hareketli ve Statik Nesneler
Devam etmeden önce, sizi hareketli ve statik nesnelere tanıtmak istiyorum. Bunlar gerçek DDD terimleri değil, domain modelinin farklı bölümlerini düşündüğümde kendim kullandığım bir şey. Benim dünyamda, taşınabilir bir nesne, birden fazla örneği olabilen ve uygulamanın farklı bölümleri arasında geçirilebilen herhangi bir nesnedir. Değer nesneleri, varlıklar ve domain eventlerinin tümü taşınabilir nesnelerdir.
Öte yandan, statik bir nesne, her zaman tek bir yerde duran ve uygulamanın diğer bölümleri tarafından çağrılan ancak nadiren (diğer statik nesnelere enjekte edilenler hariç) geçirilen singleton (veya pooled bir kaynak) 'tır. Repositoryler, domain servisleri ve factorylerin tümü statik nesnelerdir.
Bu fark önemlidir çünkü nesneler arasında ne tür ilişkileriniz olabileceğini belirler. Statik nesneler, diğer statik nesnelere ve taşınabilir nesnelere referans tutabilir.
Taşınabilir nesneler, diğer hareketli nesnelerin referansını tutabilir. Ancak, hareketli bir nesne hiçbir zaman statik bir nesneye başvuru yapamaz. Taşınabilir bir nesnenin statik bir nesneyle etkileşime girmesi gerekiyorsa, statik nesnenin kendisiyle etkileşime girecek metoda bir metod parametresi olarak iletilmesi gerekir. Bu, taşınabilir nesneleri daha taşınabilir ve bağımsız bir hale getirir, çünkü her seferinde deserialize ettiğimizde statik nesnelere herhangi bir referansa bakmanız ve enjekte etmeniz gerekmez.
Diğer Domain Nesneleri
DDD kodla çalışırken, bir sınıfın değer nesnesine, varlık veya domain event kalıbına gerçekten uymadığı durumlarda karşılaşacağınız zamanlar olacaktır. Deneyimlerime göre, bu genellikle aşağıdaki durumlarda olur:
- Harici bir sistemden herhangi bir bilgi (= başka bir sınırlı bağlam(bounded context)). Bilgiler sizin bakış açınızdan değiştirilemez, ancak benzersiz bir şekilde tanımlamak için kullanılan bir global ID'ye sahiptir.
- Diğer varlıkları tanımlamak için kullanılan verileri yazın (Vaughn Vernon, bu nesnelere standart tipler olarak adlandırır). Bu nesneler global ID'lere sahiptir ve bir dereceye kadar değişebilir olabilir, ancak uygulamanın kendisinin tüm pratik amaçları için değişmezdirler(immutable).
- Denetim girişlerini veya domain eventlerini veritabanında depolamak için kullanılan altyapı altyapı - framework düzeyindeki varlıklar. Global ID'leri olabilir veya olmayabilir ve kullanım durumuna bağlı olarak değişebilir(mutable) veya değişmeyebilir(immutable).
User.java (identity management)
public class User extends AggregateRoot {
private String userName;
private String firstName;
private String lastName;
private Instant validFrom;
private Instant validTo;
private boolean disabled;
private Instant nextPasswordChange;
private List passwordHistory;
// Getters, setters and business logic omitted
}
Çalışan yönetimi bağlamında, yalnızca kullanıcı ID'sne ve adına ihtiyacımız var. Kullanıcı ID ile benzersiz bir şekilde tanımlanır, ancak ad kullanıcı arayüzünde gösterilir. Açıkçası herhangi bir kullanıcı bilgisini değiştiremeyiz, böylece kullanıcı bilgileri değiştirilemez. Kod şöyle görünür:
User.java (employee management)
public class User implements IdentifiableDomainObject {
private final UserId userId;
private final String firstName;
private final String lastName;
@JsonCreator // We can deserialize the incoming JSON directly into an instance of this class.
public User(String userId, String firstName, String lastName) {
// Populate fields, convert incoming userId string parameter into a UserId value object instance.
}
public String getFullName() {
return String.format("%s %s", firstName, lastName);
}
// Other getters omitted.
public boolean equals(Object o) {
// Check userId only
}
public int hashCode() {
// Calculate based on userId only
}
}
Repositories
Artık domain modelinin tüm hareketli nesnelerini ele aldık ve artık statik olanlara geçme zamanı. İlk statik nesne repository'dir. Bir repository, agregaların peristence konteynırıdır. Bir repository'e kaydedilen tüm agregalar, sistem yeniden başlatıldıktan sonra bile daha sonra oradan alınabilir.
Bir repository'nin en azından aşağıdaki yetenekleri olmalıdır:
- Bir tür veri depolamada bir agregayı tamamen kaydetme yeteneği.
- Bir agregayı, ID'sine göre bütünüyle alabilme.
- Bir agregayo ID'sine göre tamamen silme yeteneği.
public interface OrderRepository {
Optional get(OrderId id);
boolean contains(OrderID id);
void add(Order order);
void remove(Order order);
Page search(OrderSpecification specification, int offset, int size);
}
// Would be used like this:
public void doSomethingWithOrder(OrderId id) {
orderRepository.get(id).ifPresent(order -> order.doSomething());
// Changes will be automatically persisted.
}
public interface OrderRepository {
Optional findById(OrderId id);
boolean exists(OrderId id);
Order save(Order order);
void delete(Order order);
Page findAll(OrderSpecification specification, int offset, int size);
}
// Would be used like this:
public void doSomethingWithOrder(OrderId id) {
orderRepository.findById(id).ifPresent(order -> {
order.doSomething();
orderRepository.save(order);
});
}
CQRS Hakkında Bir Not
Repository'ler her zaman agregaları kaydeder ve alır. Bu, nasıl uygulandıklarına ve her bir agrega için oluşturulması gereken nesne grafiklerinin boyutuna bağlı olarak oldukça yavaş olabileceği anlamına gelir. Bu, UX açısından sorunlu olabilir ve özellikle iki kullanım durumu akla gelir. Birincisi, yalnızca bir veya iki özellik kullanarak agrega listesini göstermek istediğiniz küçük bir listedir. Yalnızca birkaç öznitelik değerine ihtiyacınız olduğunda eksiksiz bir nesne grafiği oluşturmak zaman kaybı ve bilgi işlem kaynaklarıdır ve genellikle yavaş bir kullanıcı deneyimine yol açar. Başka bir durum, bir listede tek bir öğeyi göstermek için birden fazla agregadaki verileri birleştirmeniz gerektiğidir. Bu, daha kötü performansa neden olabilir.
Veri setleri ve agregalar küçük olduğu sürece, performans kaybı kabul edilebilir, ancak performansın kabul edilebilir olmadığı zaman gelirse bir çözüm vardır: Komut Sorgu Sorumluluk Ayrımı (Command Query Responsibility Segregation)(CQRS).
- Sistemin durumunu değiştiren tüm kullanıcı işlemleri repository'lerden normal şekilde geçer.
- Tüm sorgular repository'leri atlar ve doğrudan temel alınan veritabanına gider ve yalnızca gerekli verileri alır ve başka bir şey almaz.
- Gerekirse, kullanıcı arayüzündeki her görünüm için ayrı sorgu nesneleri bile tasarlayabilirsiniz
- Sorgu nesneleri tarafından döndürülen Veri Aktarım Nesneleri (DTO), agrega ID'lerini içermelidir, böylece değişiklik yapma zamanı geldiğinde doğru agrega repository'den alınabilir.
- Durum bilgisi tutmazlar(stateless)
- Son derece cohesive'lerdir (yani sadece bir şey yapmakta uzmanlaşmışlardır)
- Başka bir yerde doğal olarak uymayan iş mantığı içerirler.
- Diğer domain servisleriyle ve bir ölçüde repository'lerle etkileşime girebilirler.
- Domain event'lerini yayınlayabilirler.
public class TransactionValidator {
public boolean isValid(Money amount, Account from, Account to) {
if (!from.getCurrency().equals(amount.getCurrency())) {
return false;
}
if (!to.getCurrency().equals(amount.getCurrency())) {
return false;
}
if (from.getBalance().isLessThan(amount)) {
return false;
}
if (amount.isGreaterThan(someThreshold)) {
return false;
}
return true;
}
}
public interface CurrencyExchangeService {
Money convertToCurrency(Money currentAmount, Currency desiredCurrency);
}
Domain modeli, örneğin bir dependency injection framework kullanılarak bağlandığında, bu arabirimin doğru uygulamasını enjekte edebilirsiniz. Local cache çağıran bir servvise, veya uzak bir web servisi çağıran bir servise sahip olabilirsiniz; bir diğeri yalnızca test amacıyla kullanılıabilir vb.
Factories
- İş mantığı agreganın oluşturulmasında rol oynar
- Agregatın yapısı ve içeriği giriş verilerine bağlı olarak çok farklı olabilir
- Giriş verileri o kadar geniştir ki, oluşturucu modeli (veya benzer bir şey) gereklidir
- Factory, sınırlı bir bağlamdan(bounded context) diğerine çeviri yapar.
public class ShipmentRecipientFactory {
private final PostOfficeRepository postOfficeRepository;
private final StreetAddressRepository streetAddressRepository;
// Initializing constructor omitted
ShipmentRecipient createShipmentRecipient(Customer customer) {
var postOffice = postOfficeRepository.findByPostalCode(customer.postalCode());
var streetAddress = streetAddressRepository.findByPostOfficeAndName(postOffice, customer.streetAddress());
var recipient = new ShipmentRecipient(customer.fullName(), streetAddress);
recipient.associateWithCustomer(customer.id());
return recipient;
}
}
- foo.bar.domain.model.services
AuthenticationService
PasswordEncoder
- foo.bar.domain.model.repositories
UserRepository
RoleRepository
- foo.bar.domain.model.entities
User
Role
- foo.bar.domain.model.valueobjects
UserId
RoleId
UserName
- foo.bar.domain.model.authentication
AuthenticationService
- foo.bar.domain.model.user
User
UserRepository
UserId
UserName
PasswordEncoder
- foo.bar.domain.model.role
Role
RoleRepository
RoleId