Ghid Didactic Complet: De la Junior la Mid-Level
Acest dashboard interactiv este conceput pentru a te pregăti pentru interviurile tehnice și pentru provocările reale din producție. Vom explora arhitectura REST, ecosistemul Spring Boot, optimizarea bazelor de date și integrarea cu React.
Arhitectură Software & Design
Fluxul Datelor (Request Flow)
Browser / Axios
API Layer
Business Logic
Data Access
Database
Spring Boot Core & Clean Code
Analiză detaliată a codului. Comentariile explică conceptele de "Inversion of Control", "Dependency Injection" și "Transaction Management".
// 1. @RestController: Spune framework-ului că acest bean gestionează cereri HTTP // și că răspunsul metodelor va fi automat serializat în JSON (via Jackson). @RestController // 2. @RequestMapping: Prefixul comun pentru toate endpoint-urile din clasă. @RequestMapping("/api/orders") public class OrderController { // Folosim 'final' pentru a garanta imutabilitatea referinței după construcție. private final OrderService service; // 3. Constructor Injection: Modul preferat de Dependency Injection. // Beneficii: Testabilitate ușoară (poți da un mock în teste unitare), // asigură că bean-ul nu este creat într-o stare invalidă (fără dependențe). public OrderController(OrderService service) { this.service = service; } // 4. ResponseEntity<?>: Wrapper HTTP complet (Status, Headers, Body). @PostMapping public ResponseEntity<OrderResponse> create( // 5. @Valid: Declanșează validarea Bean Validation (Hibernate Validator) // înainte ca metoda să fie executată. Dacă DTO-ul e invalid, // Spring aruncă automat MethodArgumentNotValidException -> 400 Bad Request. @Valid @RequestBody OrderRequest req) { OrderResponse response = service.placeOrder(req); // Returnăm 200 OK (sau 201 Created) explicit. return ResponseEntity.ok(response); } }
// Java 17+ Record: // - O clasă specială IMUTABILĂ destinată stocării de date. // - Generează automat: constructor, getters (numele câmpului), equals(), hashCode(), toString(). // - Elimină boilerplate-ul clasic (Lombok nu mai e strict necesar pentru DTO-uri). public record OrderRequest( // Validare: Nu poate fi null. @NotNull(message = "User ID is required") Long userId, // Validare: Lista nu poate fi null și trebuie să aibă cel puțin un element. @NotEmpty(message = "Cart cannot be empty") List<OrderItemDto> items, // Opțional, poate fi null. String promoCode ) {}
// @Service: Componentă Spring care conține Logica de Business (Domain Logic). @Service public class OrderService { private final ProductRepository productRepo; private final OrderRepository orderRepo; // ACID - Atomicity, Consistency, Isolation, Durability. // @Transactional: // 1. Deschide o tranzacție la începutul metodei. // 2. Face Commit la final dacă nu apar erori. // 3. Face ROLLBACK automat dacă apare o RuntimeException (ex: stoc insuficient). @Transactional public OrderResponse placeOrder(OrderRequest req) { // Pas 1: Transformare DTO -> Entitate Order order = mapper.toEntity(req); // Pas 2: Salvare comandă (Write operation) orderRepo.save(order); // Pas 3: Scădere stoc. // Dacă metoda decreaseStock aruncă 'InsufficientStockException', // tranzacția se anulează, iar 'orderRepo.save' de mai sus este șters (Rollback). productRepo.decreaseStock(req.items()); return mapper.toDto(order); } }
2 Concepte Fundamentale: HTTP & Spring Boot
Înțelegerea protocolului HTTP și a containerului IoC (Inversion of Control) din Spring este obligatorie. Mai jos sunt comparații interactive esențiale.
HTTP: PUT vs PATCH
Selectează o metodă pentru a vedea cum afectează resursa de mai jos.
Spring: @Component (Component Scanning)
@Component se pune pe clasele tale
și permite Spring să le descopere automat prin
Component Scanning.
@Service, @Repository,
@Controller sunt specializări semantice ale
@Component (doar semantică + funcționalitate suplimentară).
@Service
public class UserService {
private final UserRepository repo;
public UserService(UserRepository repo) {
this.repo = repo;
}
}
Când ai cod propriu și instanțiere simplă, fără logică specială de configurare.
Spring: @Bean (Configurare explicită)
@Bean se pune pe metode într-o
clasă @Configuration și oferă
control total asupra instanțierii.
Folosit des pentru librării externe (unde nu
poți adăuga @Component) sau pentru obiecte cu
configurare complexă.
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
@Bean poate fi folosit și pentru
clasele tale, dacă ai nevoie de logică
specială la creare.
@Bean
public UserService userService(UserRepository repo) {
return new UserService(repo);
}
Diferența nu este dacă obiectul e intern sau extern, ci cine controlează instanțierea.
Spring: De ce @Service și @Repository sunt Bean-uri?
@Service și @Repository sunt
specializări ale @Component.
Spring le detectează automat și le înregistrează în context.
@Service
public class UserService {}
@Repository
public class UserRepository {}
Bean-urile NU sunt doar obiecte din librării externe. Clasele tale sunt Bean-uri pentru că Spring le gestionează, nu pentru că ar fi externe.
Un obiect creat manual cu new sau o clasă fără
adnotări Spring NU este un Bean.
public class UserDto {} // ❌ nu e Bean
UserService s = new UserService(); // ❌ nu e Bean
„Un Spring Bean este orice obiect gestionat de Spring Container.
@Service și @Repository sunt Bean-uri
prin Component Scanning, iar @Bean este doar o altă metodă
de definire.”
Spring: Ce este un Bean (dincolo de adnotări)
Un Spring Bean nu este doar un obiect creat de Spring, ci un obiect asupra căruia Spring aplică injection, lifecycle, proxy-uri și cross-cutting concerns.
- rezolvă dependențe (Dependency Injection)
- controlează ciclul de viață (init / destroy)
- aplică proxy-uri (@Transactional, @Async)
- respectă scope-ul (singleton, request etc.)
Doar pentru că această clasă este Bean, Spring poate injecta automat dependențe și gestiona tranzacții:
@Service
public class OrderService {
private final OrderRepository repo;
public OrderService(OrderRepository repo) {
this.repo = repo;
}
@Transactional
public void placeOrder() {
repo.save(...);
}
}
Un obiect creat cu new nu beneficiază de niciuna dintre
facilitățile Spring (injectare, tranzacții, proxy-uri).
OrderService service = new OrderService(repo);
// ❌ @Transactional nu funcționează
Bean-ul este unitatea de bază a Spring.
Adnotările precum @Component sau @Bean
sunt doar moduri diferite de a spune:
„Spring, te rog gestionează acest obiect”.
Spring & Lombok: @AllArgsConstructor vs @RequiredArgsConstructor
Generează constructor cu toate câmpurile. Dacă ai câmpuri care nu sunt Bean-uri Spring
(ex: String), aplicația va eșua la runtime.
@AllArgsConstructor
@Service
public class UserService {
private UserRepository repo;
private String name; // ❌ nu e Bean
}
Generează constructor doar pentru câmpurile final. Spring face automat constructor injection dacă există un singur constructor.
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository repo;
@Value("${user.name}")
private final String name;
}
Spring: @Autowired pe Field vs Constructor
Ușor de scris, dar nerecomandat. Ascunde dependențele, nu permite câmpuri final
și este greu de testat.
@Service
public class UserService {
@Autowired
private UserRepository repo;
}
Dependențele sunt clare, clasa este imutabilă și ușor de testat. @Autowired nu este necesar dacă există un singur constructor.
@Service
public class UserService {
private final UserRepository repo;
public UserService(UserRepository repo) {
this.repo = repo;
}
}
HTTP Status Codes Cheat Sheet
Securitate: JWT & Stateless
Concepte Cheie
Securitatea într-un REST API modern se bazează pe principiul Stateless. Nu folosim `HttpSession` pe server.
- JWT (JSON Web Token): "Biletul" de acces. Conține ID-ul userului și Rolurile, semnate criptografic.
- BCrypt: Algoritm de hashing pentru parole. Include "Salt" automat pentru a preveni atacurile Rainbow Table.
- CORS: Mecanism prin care backend-ul permite frontend-ului (de pe alt domeniu/port) să acceseze resursele.
Auth Flow
- Client trimite
POST /login. - Server verifică parola hash-uită.
- Server generează Token-ul (valabil 15 min).
- Client primește Token-ul și îl salvează (ex: localStorage).
- Client trimite headerul
Authorization: Bearer xyz...la fiecare request.
Data, Optimizare & Performanță
Scalability Curve: "The Knee"
Observație: Până la "Punctul de Saturație" (genunchiul curbei), throughput-ul crește liniar și latența e stabilă. După saturație, latența explodează exponențial (cozi de așteptare), iar throughput-ul se plafonează sau scade.
Database Indexing
B-Tree: Standard pentru egalitate/range.
Partial Index: WHERE active = true (Index mic și rapid).
SQL vs NoSQL
Caching (Redis)
@Cacheable: Cache-aside pattern. Dacă datele sunt în Redis, le returnează instant. Dacă nu, execută metoda și salvează în Redis.
React, Modern Stack & DevOps Essentials
Concepte esențiale pentru frontend, DevOps și baze de date moderne, cu exemple de cod și întrebări tip interviu.
React Router (SPA)
Client-side routing: navigare fără reload. SPA (Single Page Application).
<Routes>
<Route path="/home" element={<Home />} />
<Route path="/admin" element={<Admin />} />
</Routes>
Tip interviu: explică nested routes, protected routes și lazy loading pentru performanță.
Întrebare: „Cum previi re-render inutil în React Router?”
Răspuns: folosește React.memo, React.lazy și Suspense pentru componente mari.
Props vs State
- Props: Imutabile, date primite de la componenta părinte.
- State: Memoria internă a componentei, schimbarea declanșează re-render.
- Exemplu:
function Counter({ initial }) { const [count, setCount] = useState(initial); return <button onClick={() => setCount(count + 1)>{count}</button>; } - Întrebare interviu: „De ce props sunt read-only și când ai folosi lifting state up?” Răspuns: Pentru a sincroniza state între componente părinte și copil, eviti duplicarea datelor.
Hooks: useState & useEffect
- useState: starea locală
const [text, setText] = useState(""); - useEffect: lifecycle în componente funcționale
useEffect(() => { console.log("Component mounted"); return () => console.log("Cleanup on unmount"); }, []); - Interviu: „Ce se întâmplă dacă uiți array-ul de dependențe?” → loop infinit, memory leak
React + Axios + JWT
- JWT Stateless Authentication → backend nu ține session
- Axios Interceptors → injectare token automat
axios.interceptors.request.use(config => { config.headers.Authorization = `Bearer ${token}`; return config; }); - Întrebare interviu: „Cum securizezi refresh token-urile și accesul la API?” Răspuns: stochezi refresh token server-side, access token expiră rapid.
Docker & DevOps
- Containerizare → izolarea aplicațiilor
docker build -t my-app . docker run -p 8080:8080 my-app
- docker-compose pentru backend + DB + Redis
version: '3.8' services: backend: build: ./backend ports: - "8080:8080" postgres: image: postgres:15 environment: POSTGRES_PASSWORD=secret redis: image: redis:7 - Întrebare interviu: „Cum scalezi servicii multiple cu Docker?” Răspuns: folosești multiple replicas + load balancer sau Kubernetes.
PostgreSQL Essentials
- ACID → Atomicitate, Consistență, Izolare, Durabilitate
- JSONB → stocare rapidă și indexabilă
SELECT data->>'name' FROM products WHERE data @> '{"category":"electronics"}'; - Partial Index
CREATE INDEX idx_active_products ON products(id) WHERE active = true;
- Full Text Search → tsvector + GIN pentru căutări eficiente
- Întrebare interviu: „De ce PostgreSQL vs MySQL?” Răspuns: extensii, JSONB, Partial Index, ACID garantat, tipuri complexe.
PostgreSQL Indexing & Transactions
- B-Tree → egalitate și sortare scalară
- GIN → arrays, JSONB, full text search
- BRIN → mari volume de date, range scan optimizat
- Transactions → BEGIN / COMMIT / ROLLBACK
BEGIN; UPDATE accounts SET balance = balance - 100 WHERE id = 1; UPDATE accounts SET balance = balance + 100 WHERE id = 2; COMMIT;
- Întrebare interviu: „Cum eviți deadlocks în PostgreSQL?” Răspuns: tranzacții scurte, indexuri corecte, ordine constantă de UPDATE, folosirea EXPLAIN ANALYZE pentru optimizare.
Structuri de Date (Deep Dive)
Analiză comparativă pentru interviurile tehnice.
| Structură | Implementare Internă | Complexitate (Search/Add) | Când să folosești? |
|---|---|---|---|
| ArrayList | Array redimensionabil (crește cu 50% când e plin). | Get: O(1) / Add: O(1)* | Acces rapid prin index. Iterare frecventă. Ștergeri lente. |
| LinkedList | Noduri dublu înlănțuite (pointeri prev/next). | Get: O(n) / Add: O(1) | Inserări/Ștergeri frecvente la capete (Queue/Deque). |
| HashMap | Buckets Array. Coliziuni rezolvate prin LinkedList -> Red-Black Tree (Java 8+). | Avg: O(1) / Worst: O(log n) | Key-Value lookup. Nu păstrează ordinea. |
| TreeMap | Red-Black Tree (Arbore binar echilibrat). | All: O(log n) | Chei sortate natural. Range queries. |
| HashSet | Folosește un HashMap intern unde valoarea e o constantă. |
Avg: O(1) | Elemente unice. Verificare rapidă de apartenență. |
Algoritmică & Interviu Coding
7 Tipuri de probleme esențiale. Soluțiile sunt în dropdown.
1. Reverse Polish Notation (RPN)
Stack • O(n)Se dă o expresie aritmetică în Reverse Polish Notation, reprezentată ca un array de string-uri, cu numere întregi și operatorii +, -, *, /. Scopul este să evaluezi expresia și să returnezi rezultatul numeric. Exemplu: ["2", "1", "+", "3", "*"] corespunde expresiei infix (2 + 1) * 3 și rezultatul este 9. Evaluează expresia: `["2", "1", "+", "3", "*"]` -> `(2+1)*3` = 9.
Vezi Soluția
public int evalRPN(String[] tokens) {
Stack<Integer> stack = new Stack<>();
for (String t : tokens) {
if ("+-*/".contains(t)) {
int b = stack.pop(), a = stack.pop();
switch (t) {
case "+" -> stack.push(a + b);
case "-" -> stack.push(a - b);
// ...
}
} else stack.push(Integer.parseInt(t));
}
return stack.pop();
}
2. Longest Increasing Subsequence (LIS)
DP & Binary SearchSe dă un array de numere întregi. Trebuie să găsești lungimea celui mai lung subșir strict crescător. Elementele subșirului nu trebuie să fie consecutive în array, dar ordinea trebuie păstrată.
Exemplu: [10, 9, 2, 5, 3, 7, 101, 18] → cel mai lung subșir crescător este [2, 3, 7, 101] și lungimea sa este 4.
Soluție 1: Dynamic Programming O(n²)
public int lengthOfLIS(int[] nums) {
int[] dp = new int[nums.length];
Arrays.fill(dp, 1);
int max = 1;
for(int i=1; i<nums.length; i++) {
for(int j=0; j<i; j++) {
if(nums[i] > nums[j]) dp[i] = Math.max(dp[i], dp[j]+1);
}
max = Math.max(max, dp[i]);
}
return max;
}Soluție 2: Binary Search O(n log n)
public int lengthOfLIS(int[] nums) {
int[] tails = new int[nums.length];
int size = 0;
for (int x : nums) {
int i = 0, j = size;
while (i != j) {
int m = (i + j) / 2;
if (tails[m] < x) i = m + 1;
else j = m;
}
tails[i] = x;
if (i == size) ++size;
}
return size;
}3. Longest Substring Without Repeating Characters
Sliding Window • O(n)Se dă un string. Trebuie să găsești lungimea maximă a unui substring care conține doar caractere unice. Exemplu: "abcabcbb" → cel mai lung substring fără caractere repetate este "abc" și lungimea sa este 3.
Vezi Soluția
public int lengthOfLongestSubstring(String s) {
int max = 0, left = 0;
Map<Character, Integer> map = new HashMap<>();
for (int right = 0; right < s.length(); right++) {
char c = s.charAt(right);
if (map.containsKey(c)) {
left = Math.max(left, map.get(c) + 1);
}
map.put(c, right);
max = Math.max(max, right - left + 1);
}
return max;
}
4. Number of Islands
Grid DFS • O(m*n)Se dă un grid 2D format din caractere '1' (pământ) și '0' (apă). Trebuie să numeri câte insule există în grid. O insulă este o grupare de celule cu '1' conectate orizontal sau vertical.Exemplu:
grid = [
["1","1","0","0","0"],
["1","1","0","0","0"],
["0","0","1","0","0"],
["0","0","0","1","1"]
]
Rezultat: 3 insule.
Vezi Soluția
public int numIslands(char[][] grid) {
int count = 0;
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[0].length; j++) {
if (grid[i][j] == '1') {
dfs(grid, i, j);
count++;
}
}
}
return count;
}
private void dfs(char[][] grid, int i, int j) {
if (i < 0 || i >= grid.length || j < 0 || j >= grid[0].length || grid[i][j] != '1') return;
grid[i][j] = '0'; // Mark as visited
dfs(grid, i+1, j); dfs(grid, i-1, j);
dfs(grid, i, j+1); dfs(grid, i, j-1);
}
5. Merge Intervals
Sorting • O(n log n)Se dă un array de intervale [start, end]. Trebuie să combini toate intervalele care se suprapun și să returnezi un array cu intervalele unite. Exemplu:
intervals = [[1,3],[2,6],[8,10],[15,18]]
Rezultat: [[1,6],[8,10],[15,18]]
Explicație: Intervalele [1,3] și [2,6] se suprapun, deci se combină în [1,6].
Soluție
public int[][] merge(int[][] intervals) {
Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
LinkedList<int[]> merged = new LinkedList<>();
for (int[] interval : intervals) {
if (merged.isEmpty() || merged.getLast()[1] < interval[0])
merged.add(interval);
else
merged.getLast()[1] = Math.max(merged.getLast()[1], interval[1]);
}
return merged.toArray(new int[merged.size()][]);
}6. Invert Binary Tree
Recursion • O(n)Se dă un arbore binar. Trebuie să inversezi toate nodurile, astfel încât subarborele stâng să devină drept și subarborele drept să devină stâng la fiecare nod. Exemplu:
Input: 4
/ \
2 7
/ \ / \
1 3 6 9
Output: 4
/ \
7 2
/ \ / \
9 6 3 1
Toate nodurile stânga/dreapta sunt inversate recursiv.
Soluție
public TreeNode invertTree(TreeNode root) {
if (root == null) return null;
TreeNode temp = root.left;
root.left = invertTree(root.right);
root.right = invertTree(temp);
return root;
}7. Top K Frequent Elements
Heap • O(n log k)Se dă un array de numere și un număr k. Trebuie să găsești cele k elemente cu cea mai mare frecvență din array.
Exemplu:
nums = [1,1,1,2,2,3], k = 2
Rezultat: [1,2]
Explicație: 1 apare de 3 ori, 2 apare de 2 ori, iar 3 apare o singură dată. Cele mai frecvente 2 elemente sunt [1,2].
Soluție
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> count = new HashMap<>();
for (int n : nums) count.put(n, count.getOrDefault(n, 0) + 1);
PriorityQueue<Integer> heap = new PriorityQueue<>((a, b) -> count.get(a) - count.get(b));
for (int n : count.keySet()) {
heap.add(n);
if (heap.size() > k) heap.poll();
}
return heap.stream().mapToInt(i -> i).toArray();
}Simulator Interviu (50+)
Soft Skills & Cariera
Cum să treci de interviul HR și tehnic și să vinzi eficient skill-urile tale.
Cum prezinți Proiectul?
Nu spune doar "am făcut un magazin". Folosește structura clară: Problema – Soluția – Tehnologii – Arhitectură. Exemplu concret pentru un proiect Fullstack:
- 1. Problema: "Am vrut să construiesc un magazin online scalabil, cu mii de produse și chat realtime între clienți și suport."
- 2. Soluția: "Am dezvoltat o aplicație Cloud-Native cu arhitectură modulară monolitică, separând clar frontend, backend și caching."
- 3. Tehnologii: "Backend Spring Boot cu JWT Stateless Security, PostgreSQL pe Neon, Redis pe Upstash, React pe Netlify, Firestore pentru chat realtime, Docker pentru containere, Swagger pentru documentație API."
- 4. Arhitectură: "Am aplicat Clean Code, principii SOLID, N-Tier Architecture. Modul monolitic modular pentru scalabilitate, testabilitate și mentenanță ușoară."
- 5. Rezultate / Impact: "Cache-ul Redis a redus timpul de răspuns cu 30%, Partial Index în PostgreSQL a accelerat interogările cu 40%, chat-ul Firestore a permis interacțiune realtime fără blocaje."
Acest exemplu arată clar impactul tehnic și valoarea adusă. Este un punct esențial pentru interviu.
Structura CV-ului Tehnic și Branding Personal
- Adaptează CV-ul tău în funcție de poziția și firma pentru care aplici. E.g. Dacă aplici pentru o poziție de Java cu React prioritizează proiectele și skillurile relevante. Nu-ți începe CV în acest caz cu un proiect de Arduino, Embedded sau Grafică. Primul proiect din CV trebuie să fie cel mai relevant, urmat de celelalte în ordine descrescătoare.
- Header: Nume complet, poza profesională, GitHub, LinkedIn, email profesional (ex: prenume.nume@email.com).
- Skills: Grupează: Languages (Java, JavaScript), Frameworks (Spring, React), Tools (Docker, Git, AWS/Cloud), Databases (Postgres, Firestore, Redis).
- Proiecte (CRUCIAL):
"E-Shop Full Stack | Java, Spring, React, PostgreSQL, Redis, Firestore, Docker, Netlify, JWT Security"
- JWT Stateless Authentication & Role-Based Access Control.
- Optimizări SQL cu Partial Index (viteză +40%).
- Redis Cache pentru reducerea load-ului pe DB.
- Firestore pentru chat realtime între utilizatori.
- Swagger pentru documentația API.
- Deploy complet pe Render & Netlify. - Educație / Certificări: Include relevante (Java, Spring, Cloud, Docker).
- Tehnici de vânzare personală:
- La HR: arată cum soft skills și teamwork contribuie la echipă.
- La tech: explică cum skill-urile tale aduc valoare concretă în firmă.
- Exemplu întrebare pentru branding personal: "Cum ați vedea că experiența mea fullstack poate accelera livrarea proiectelor în echipa voastră?"
- Social & Contact: LinkedIn cu profil complet, GitHub activ, email profesionist. Evită nickname-uri sau cuvinte amuzante (ex: spiderman123).
Întrebări frecvente HR
- Spune-ne despre tine: Prezintă proiectul Fullstack, focus pe impact și tehnologii.
- De ce vrei să lucrezi aici? Leagă-ți skill-urile de specificul companiei și de proiectele lor, ideal de o pasiune pe care o ai în zona domeniului lor de activitate.
- Cum te descurci cu deadline-uri strânse? Exemplu concret din proiectul magazinului online (deploy rapid, testing automat).
- Alocă timp înainte de fiecare interviu pentru a studia firma la care aplici și domeniul ei de activitate.
Întrebări frecvente tehnice
- Explică arhitectura modul monolith + N-Tier și avantajele sale.
- Cum ai folosit Redis și PostgreSQL împreună pentru performanță?
- JWT vs session-based authentication, de ce ai ales Stateless în Spring Boot?
- Descrie modul de deploy: Netlify pentru frontend, Render pentru backend.
- Explică integrarea Firestore realtime chat și cum ai gestionat securitatea datelor.