Final Project
Final Project Assessment and Course Completion
আপনি একটি free preview lesson দেখছেন।
Lesson Overview
এই lesson পুরো course-এর final assessment।
এখানে নতুন Java feature শেখানো হবে না।
বরং আপনাকে independently prove করতে হবে যে আপনি:
Java syntax
Object-oriented programming
Collections
Generics
Exceptions
File I/O
Value objects
Records
Clean design
Dependency design
একসঙ্গে ব্যবহার করতে পারেন।
Final challenge:
Build the LiveKlass Course Enrollment System Independently
আগের lessons reference হিসেবে ব্যবহার করা যাবে, কিন্তু goal হলো নিজের design reasoning দিয়ে applicationটি rebuild করা।
একটি professional foundation তৈরি হয়েছে কিনা তার সবচেয়ে ভালো test:
Can you build it again
without copying every previous implementation?
Final Project Requirements
Application must support:
1. Create course
2. Add lesson
3. Publish course
4. Archive course
5. List courses
6. Register learner
7. List learners
8. Enroll learner
9. Complete enrollment
10. Cancel enrollment
11. List enrollments
12. Persist data across restarts
Application interface:
Console
Persistence:
File-based
External frameworks:
None
Mandatory Domain Types
Your solution should contain equivalents of:
CourseCode
LessonId
Lesson
CourseStatus
Course
LearnerId
EmailAddress
Learner
EnrollmentId
EnrollmentStatus
Enrollment
You may rename types if the new names clearly communicate the same domain concepts।
Strong Value Types
These should not remain raw primitives everywhere:
Course identifier
Lesson identifier
Learner identifier
Enrollment identifier
Email address
Example:
public record LearnerId(
long value
) {
public LearnerId {
if (value <= 0) {
throw new IllegalArgumentException(
"Learner id must be positive."
);
}
}
}
The purpose is not merely wrapping a long।
The type should guarantee:
Meaning
Validation
Stable equality
Course Rules
A new course:
Starts as DRAFT
Course code:
Required
Normalized
Stable
Title:
Required
Non-blank
Price:
Cannot be negative
Lessons:
Must have unique LessonId values
Can only be added while DRAFT
Publication:
Only DRAFT can publish
At least one lesson is required
Archiving:
Only PUBLISHED can archive
Expected Course API
A strong design may expose:
course.addLesson(
lesson
);
course.publish();
course.archive();
Avoid APIs such as:
course.setStatus(...);
course.setLessons(...);
that allow callers to bypass invariants।
Enrollment Rules
New enrollment:
ACTIVE
Transitions:
ACTIVE → COMPLETED
ACTIVE → CANCELLED
Invalid:
COMPLETED → CANCELLED
CANCELLED → COMPLETED
COMPLETED → COMPLETED
Expected API:
enrollment.complete();
enrollment.cancel();
Cross-Entity Enrollment Rules
Before enrollment:
Learner must exist
Course must exist
Course must be PUBLISHED
Enrollment ID must be unique
Learner cannot already be enrolled in the same course
These rules require multiple repositories।
Therefore they should normally be coordinated by:
EnrollmentService
not by Enrollment alone।
Repository Requirements
Implement:
CourseRepository
LearnerRepository
EnrollmentRepository
Example:
public interface CourseRepository {
void save(
Course course
);
Course findByCode(
CourseCode courseCode
);
List<Course> findAll();
}
Services should depend on these interfaces rather than directly depending on:
FileCourseRepository
FileLearnerRepository
FileEnrollmentRepository
File Persistence Requirements
Store data under something like:
data/
├── courses/
├── learners/
└── enrollments/
Use:
Path
Files
Properties
StandardCharsets.UTF_8
Resources must be managed with:
try-with-resources
File Save Requirements
A strong implementation should use:
Serialize
↓
Write temporary file
↓
Close writer
↓
Replace target file
Attempt:
ATOMIC_MOVE
when supported।
Fallback safely if atomic move is unavailable।
Restore Requirements
Repository must be able to reconstruct stored lifecycle state।
Example:
Course.restore(...)
and:
Enrollment.restore(...)
Restore should not mean:
Skip validation
Corrupted stored state must still fail।
Missing vs Corrupted Data
Your repository should distinguish:
Entity file missing
from:
Entity file exists but contains invalid data
Missing entity may produce:
null
at repository level।
Corrupted data should produce something meaningful such as:
StoredDataCorruptionException
Console Requirements
The console should:
Read input
Parse input
Create strong domain values
Call services
Display results
Display errors
It should not:
Write files directly
Change entity state directly
Implement duplicate checks
Implement publication rules
Required Application Flow
A typical successful scenario:
Create course
↓
Add lesson
↓
Publish course
↓
Register learner
↓
Enroll learner
↓
Complete enrollment
↓
Restart application
↓
Verify persisted state
Hidden Edge Cases
Do not test only the happy path।
Your implementation should handle these cases correctly।
Edge Case 1: Equivalent Course Codes
Create:
java-oop
then:
JAVA-OOP
Expected:
Duplicate course rejected
because normalized values represent the same CourseCode।
Edge Case 2: Empty Course Publication
Create course and immediately publish।
Expected:
Rejected
because it has no lesson।
Edge Case 3: Duplicate Lesson ID
Add:
LessonId 1
twice with different titles।
Expected:
Rejected
because duplicate rule is based on ID।
Edge Case 4: Modify Published Course
Publish course, then call:
addLesson(...)
Expected:
Rejected
Edge Case 5: Archive Draft Course
Expected:
Rejected
Edge Case 6: Duplicate Learner Email
Register:
subu@example.com
then:
SUBU@EXAMPLE.COM
Under this project's normalization policy:
Rejected
Edge Case 7: Enroll into Draft Course
Expected:
Rejected
Edge Case 8: Duplicate Enrollment ID
Two different enrollment requests use:
EnrollmentId 1001
Expected:
Rejected
Edge Case 9: Duplicate Learner-Course Pair
Same learner and course with another enrollment ID।
Expected:
Rejected
Edge Case 10: Complete Twice
enrollment.complete();
enrollment.complete();
Second call:
Rejected
Edge Case 11: Cancel Completed Enrollment
Expected:
Rejected
Edge Case 12: Missing Stored Entity
Request unknown course।
Expected application behavior:
CourseNotFoundException
or equivalent।
Edge Case 13: Corrupted Enum
Stored file:
status=SOMETHING
Expected:
Storage corruption failure
not:
Entity not found
Edge Case 14: Corrupted Number
Stored file:
priceInPaisa=hello
Expected:
Storage corruption failure
Edge Case 15: Restart
Create and mutate data।
Exit application।
Run it again।
Expected:
Latest state remains available
Design Review Challenge
For each code example, identify the problem।
Challenge 1
public class Course {
public List<Lesson> lessons =
new ArrayList<>();
public String status;
}
Problems:
Public mutable state
Weak String status
No encapsulation
Rules can be bypassed
Challenge 2
public void publishCourse(
String code
) {
FileCourseRepository repository =
new FileCourseRepository(
Path.of(
"data"
)
);
...
}
Problem:
Hard-coded dependency
Better:
private final CourseRepository repository;
supplied through constructor।
Challenge 3
course.setStatus(
CourseStatus.PUBLISHED
);
Problem:
State transition rules can be bypassed
Better:
course.publish();
Challenge 4
public List<Lesson> getLessons() {
return lessons;
}
Problem:
Internal mutable collection leaked
Better:
return List.copyOf(
lessons
);
Challenge 5
catch (
IOException exception
) {
return null;
}
Problem:
Storage failure incorrectly appears as not-found
Challenge 6
public class CourseService
extends FileCourseRepository {
}
Problem:
Incorrect inheritance relationship
Course service:
uses a repository
It is not a repository।
Independent Refactoring Challenge
Start with this intentionally weak code:
public class App {
public List<Course> courses =
new ArrayList<>();
public void create(
String code,
String title,
long price,
boolean published
) {
Course course =
new Course();
course.code =
code;
course.title =
title;
course.price =
price;
course.published =
published;
courses.add(
course
);
}
}
Refactor it until:
- Course has encapsulated state
- Course code becomes a value type
- Status becomes an enum
- Publication becomes behavior
- Collection ownership is protected
- Persistence is behind a repository
- Service coordinates creation
- Dependencies are constructor-injected
Do this without copying the project code line by line।
The goal is reasoning।
Course-Wide Assessment
The following questions cover the entire course।
Java Fundamentals
Can you explain:
- JVM, JDK, and JRE?
- Variables and data types?
- Primitive vs reference types?
- Operators?
- Conditionals?
- Loops?
- Methods?
- Scope?
Object-Oriented Programming
Can you explain and use:
- Class
- Object
- Field
- Method
- Constructor
- Encapsulation
- Inheritance
- Composition
- Polymorphism
- Abstraction
- Interface
Collections and Generics
Can you choose between:
List
Set
Map
based on requirements?
Can you explain:
Type safety
Generic classes
Generic methods
?
Can you explain why:
Map<CourseCode, Course>
is stronger than relying on unrelated raw strings?
Exceptions
Can you explain:
Exception propagation
try
catch
finally
throw
throws
checked
unchecked
?
Can you distinguish:
Validation failure
Domain failure
Not-found
Storage failure
Corrupted data
?
File I/O
Can you use:
Path
Files
BufferedReader
BufferedWriter
Properties
?
Can you explain:
UTF-8
try-with-resources
atomic replacement
path normalization
?
Equality
Can you explain:
==
equals()
hashCode()
?
Can you explain why equal objects must have equal hash codes?
Can you explain why mutable hash keys are dangerous?
Immutability
Can you explain why:
final List<T>
does not make the list immutable?
Can you use:
List.copyOf(...)
appropriately?
Can you distinguish:
Shallow immutability
Deep immutability
?
Records
Can you explain when:
record
is appropriate?
Strong candidates:
Value objects
DTOs
Snapshots
Validated IDs
Can you explain why lifecycle-heavy Course is better as a normal class in this project?
Clean Design
Can you recognize:
God class
Boolean blindness
Primitive obsession
Feature envy
Long method
Hidden dependency
Mutation leak
?
Can you refactor them without creating unnecessary architecture?
Dependency Design
Can you explain:
Dependency
Constructor injection
Composition
Repository boundary
Composition root
?
Can you perform dependency injection manually without Spring?
If yes, you understand the underlying principle Spring will later automate।
Final Self-Assessment Rubric
Score each category:
0 = Cannot explain or implement
1 = Understands concept but needs guidance
2 = Can implement independently
3 = Can explain design tradeoffs
| Area | Score |
|---|---|
| Java fundamentals | /3 |
| Methods and control flow | /3 |
| Classes and objects | /3 |
| Encapsulation | /3 |
| Inheritance and composition | /3 |
| Interfaces and abstraction | /3 |
| Generics | /3 |
| Collections | /3 |
| Enums | /3 |
| Exceptions | /3 |
| File I/O | /3 |
| Resource management | /3 |
| Equality and hashing | /3 |
| Immutability | /3 |
| Records | /3 |
| Clean method design | /3 |
| Domain modeling | /3 |
| Repository design | /3 |
| Dependency injection | /3 |
| Final project implementation | /3 |
Maximum:
60
Score Interpretation
52–60
Strong foundation. Ready to move into backend development.
43–51
Good foundation. Review weaker areas while moving forward.
32–42
Core understanding exists, but rebuild the final project once more.
Below 32
Repeat important modules before moving into framework-heavy backend work.
Final Project Pass Criteria
Do not judge success only by:
Application runs
A strong submission should also satisfy:
- Important values use strong types
- Invalid state is rejected early
- Entity state is encapsulated
- No public lifecycle setters
- Collections are safely owned
- Value equality is correct
- Hash-based keys are stable
- Domain rules live with appropriate owners
- Cross-entity rules live in services
- Repository contracts are focused
- Services use constructor injection
- Concrete storage stays outside services
- File resources are closed
- UTF-8 is explicit
- Missing and corrupted data differ
- Application survives restart
- Console contains no storage/domain rules
- Class and method names communicate intent
- No unnecessary abstractions were introduced
When Are You Ready for Spring Boot?
You are ready to start Spring Boot when these ideas make sense without a framework:
CourseRepository repository =
new FileCourseRepository(
path
);
CourseService service =
new CourseService(
repository
);
You should understand:
Why repository exists
Why service receives it
Why Course does not save itself
Why Main wires dependencies
Why constructor injection is useful
Spring should automate wiring you already understand।
It should not hide concepts you never learned।
What Comes Next?
The natural next course is:
Backend Development with Java and Spring Boot
There you can build on this foundation with:
Spring Boot
REST APIs
HTTP
JSON
Controllers
Dependency Injection Container
Configuration
Validation
PostgreSQL
SQL
Transactions
JPA / persistence choices
Authentication
Testing
Docker
Observability
Production concerns
Do Not Throw Away What You Learned
Framework code can tempt developers into writing:
Controller
→ Service
→ Repository
mechanically without thinking।
The important question remains:
Who owns this rule?
A framework does not answer that for you।
Example
Even in Spring Boot, this:
course.publish();
is usually stronger than placing all publication rules inside:
CourseController
or:
CourseRepository
The Java/OOP foundation still matters।
Backend Learning Mindset
When entering Spring Boot, do not ask only:
Which annotation do I need?
Also ask:
What Java object is being created?
Who owns it?
What dependency is being injected?
Where should this rule live?
What happens when this operation fails?
What boundary am I crossing?
That is how framework knowledge becomes engineering knowledge।
Final Knowledge Check
Question 1
What is the difference between an entity and value object?
Question 2
Why should entities expose behavior instead of arbitrary setters?
Question 3
Why are immutable value objects good HashMap keys?
Question 4
Why should repositories hide storage mechanisms?
Question 5
Why should application services not duplicate entity rules?
Question 6
Why should infrastructure exceptions be translated?
Question 7
What does defensive copying protect?
Question 8
Why is constructor injection useful even without Spring?
Question 9
What is the purpose of a composition root?
Question 10
What is the most important question when designing Java software?
Final Knowledge Check Answers
Answer 1
An entity has identity and usually lifecycle; a value object is primarily defined by its value and usually uses immutable value equality।
Answer 2
Behavior methods express intent and protect valid state transitions, while arbitrary setters allow callers to bypass invariants।
Answer 3
Their equality and hash-related state stays stable after insertion।
Answer 4
So application logic depends on required capabilities instead of being coupled to files, databases, or another concrete mechanism।
Answer 5
Entity invariants should have one authoritative owner; duplication can cause inconsistent rules।
Answer 6
To preserve meaningful failure categories without leaking low-level implementation details everywhere।
Answer 7
It prevents external mutable references from unexpectedly changing an object's internal state।
Answer 8
It makes dependencies explicit, required, replaceable, and easier to test।
Answer 9
It is the place where concrete application dependencies are created and wired together।
Answer 10
A powerful recurring question is:
Who should own this responsibility?
Course Completion
You have now covered the foundation required to move from:
Writing Java statements
to:
Designing small maintainable Java applications
Throughout the course we moved from:
Variables
Conditionals
Loops
Methods
into:
Classes
Objects
Encapsulation
Interfaces
Collections
Generics
Exceptions
File I/O
and finally into:
Immutability
Equality
Records
Clean design
Repositories
Dependency injection
Application architecture
The Final Project combined those concepts into one working system।
Final Takeaway
Java expertise does not begin with memorizing every API।
A strong foundation comes from understanding:
State
Behavior
Ownership
Types
Dependencies
Boundaries
Failures
When these are clear, frameworks become much easier to understand।
The goal of this course was not only:
Learn Java syntax
It was to reach the point where you can look at a requirement and ask:
What objects exist here?
What should each object own?
What should remain immutable?
Which state transitions are valid?
Which dependency belongs behind a boundary?
How should failure be represented?
If you can answer those questions and independently rebuild the Final Project, you have completed the Java and Object-Oriented Programming Foundation.