Code Architecture Patterns - Complete Guide
Published: September 25, 2024 | Reading time: 30 minutes
Architecture Patterns Overview
Code architecture patterns provide structured approaches to organizing code:
Architecture Benefits
# Architecture Pattern Benefits
- Improved code organization
- Better maintainability
- Enhanced scalability
- Clear separation of concerns
- Easier testing
- Team collaboration
- Code reusability
MVC (Model-View-Controller) Pattern
MVC Implementation
MVC Pattern Implementation
# MVC Architecture Pattern
# 1. Model Layer
// models/User.js
class User {
constructor(id, name, email) {
this.id = id;
this.name = name;
this.email = email;
this.createdAt = new Date();
}
// Data validation
validate() {
const errors = [];
if (!this.name || this.name.trim().length === 0) {
errors.push('Name is required');
}
if (!this.email || !this.isValidEmail(this.email)) {
errors.push('Valid email is required');
}
return errors;
}
isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
// Data persistence methods
async save() {
const errors = this.validate();
if (errors.length > 0) {
throw new Error(`Validation failed: ${errors.join(', ')}`);
}
// Save to database
return await this.database.save('users', this);
}
async findById(id) {
return await this.database.findById('users', id);
}
async findAll(filters = {}) {
return await this.database.findAll('users', filters);
}
async update(data) {
Object.assign(this, data);
return await this.save();
}
async delete() {
return await this.database.delete('users', this.id);
}
}
// models/Post.js
class Post {
constructor(id, title, content, authorId) {
this.id = id;
this.title = title;
this.content = content;
this.authorId = authorId;
this.createdAt = new Date();
this.updatedAt = new Date();
}
validate() {
const errors = [];
if (!this.title || this.title.trim().length === 0) {
errors.push('Title is required');
}
if (!this.content || this.content.trim().length === 0) {
errors.push('Content is required');
}
if (!this.authorId) {
errors.push('Author ID is required');
}
return errors;
}
async save() {
const errors = this.validate();
if (errors.length > 0) {
throw new Error(`Validation failed: ${errors.join(', ')}`);
}
return await this.database.save('posts', this);
}
async findById(id) {
return await this.database.findById('posts', id);
}
async findByAuthor(authorId) {
return await this.database.findAll('posts', { authorId });
}
async update(data) {
this.updatedAt = new Date();
Object.assign(this, data);
return await this.save();
}
async delete() {
return await this.database.delete('posts', this.id);
}
}
# 2. View Layer
// views/UserView.js
class UserView {
constructor() {
this.container = document.getElementById('user-container');
}
renderUser(user) {
const userHTML = `
${this.escapeHtml(user.name)}
Email: ${this.escapeHtml(user.email)}
Created: ${this.formatDate(user.createdAt)}
`;
this.container.innerHTML = userHTML;
}
renderUserList(users) {
const usersHTML = users.map(user => `
${this.escapeHtml(user.name)}
${this.escapeHtml(user.email)}
`).join('');
this.container.innerHTML = usersHTML;
}
renderUserForm(user = null) {
const isEdit = user !== null;
const formHTML = `
`;
this.container.innerHTML = formHTML;
// Add form event listener
document.getElementById('user-form').addEventListener('submit', (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const userData = Object.fromEntries(formData);
if (isEdit) {
userController.updateUser(user.id, userData);
} else {
userController.createUser(userData);
}
});
}
showError(message) {
const errorHTML = `
`;
this.container.insertAdjacentHTML('beforebegin', errorHTML);
}
showSuccess(message) {
const successHTML = `
`;
this.container.insertAdjacentHTML('beforebegin', successHTML);
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
formatDate(date) {
return new Date(date).toLocaleDateString();
}
}
// views/PostView.js
class PostView {
constructor() {
this.container = document.getElementById('post-container');
}
renderPost(post) {
const postHTML = `
${this.escapeHtml(post.content)}
`;
this.container.innerHTML = postHTML;
}
renderPostList(posts) {
const postsHTML = posts.map(post => `
${this.escapeHtml(post.title)}
${this.escapeHtml(post.content.substring(0, 100))}...
`).join('');
this.container.innerHTML = postsHTML;
}
renderPostForm(post = null) {
const isEdit = post !== null;
const formHTML = `
`;
this.container.innerHTML = formHTML;
// Add form event listener
document.getElementById('post-form').addEventListener('submit', (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const postData = Object.fromEntries(formData);
if (isEdit) {
postController.updatePost(post.id, postData);
} else {
postController.createPost(postData);
}
});
}
showError(message) {
const errorHTML = `
`;
this.container.insertAdjacentHTML('beforebegin', errorHTML);
}
showSuccess(message) {
const successHTML = `
`;
this.container.insertAdjacentHTML('beforebegin', successHTML);
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
formatDate(date) {
return new Date(date).toLocaleDateString();
}
}
# 3. Controller Layer
// controllers/UserController.js
class UserController {
constructor() {
this.userModel = new User();
this.userView = new UserView();
this.init();
}
init() {
this.loadUsers();
}
async loadUsers() {
try {
const users = await this.userModel.findAll();
this.userView.renderUserList(users);
} catch (error) {
this.userView.showError(`Failed to load users: ${error.message}`);
}
}
async viewUser(userId) {
try {
const user = await this.userModel.findById(userId);
if (user) {
this.userView.renderUser(user);
} else {
this.userView.showError('User not found');
}
} catch (error) {
this.userView.showError(`Failed to load user: ${error.message}`);
}
}
async createUser(userData) {
try {
const user = new User(null, userData.name, userData.email);
await user.save();
this.userView.showSuccess('User created successfully');
this.loadUsers();
} catch (error) {
this.userView.showError(`Failed to create user: ${error.message}`);
}
}
async editUser(userId) {
try {
const user = await this.userModel.findById(userId);
if (user) {
this.userView.renderUserForm(user);
} else {
this.userView.showError('User not found');
}
} catch (error) {
this.userView.showError(`Failed to load user: ${error.message}`);
}
}
async updateUser(userId, userData) {
try {
const user = await this.userModel.findById(userId);
if (user) {
await user.update(userData);
this.userView.showSuccess('User updated successfully');
this.loadUsers();
} else {
this.userView.showError('User not found');
}
} catch (error) {
this.userView.showError(`Failed to update user: ${error.message}`);
}
}
async deleteUser(userId) {
try {
const user = await this.userModel.findById(userId);
if (user) {
await user.delete();
this.userView.showSuccess('User deleted successfully');
this.loadUsers();
} else {
this.userView.showError('User not found');
}
} catch (error) {
this.userView.showError(`Failed to delete user: ${error.message}`);
}
}
cancelForm() {
this.loadUsers();
}
}
// controllers/PostController.js
class PostController {
constructor() {
this.postModel = new Post();
this.postView = new PostView();
this.init();
}
init() {
this.loadPosts();
}
async loadPosts() {
try {
const posts = await this.postModel.findAll();
this.postView.renderPostList(posts);
} catch (error) {
this.postView.showError(`Failed to load posts: ${error.message}`);
}
}
async viewPost(postId) {
try {
const post = await this.postModel.findById(postId);
if (post) {
this.postView.renderPost(post);
} else {
this.postView.showError('Post not found');
}
} catch (error) {
this.postView.showError(`Failed to load post: ${error.message}`);
}
}
async createPost(postData) {
try {
const post = new Post(null, postData.title, postData.content, parseInt(postData.authorId));
await post.save();
this.postView.showSuccess('Post created successfully');
this.loadPosts();
} catch (error) {
this.postView.showError(`Failed to create post: ${error.message}`);
}
}
async editPost(postId) {
try {
const post = await this.postModel.findById(postId);
if (post) {
this.postView.renderPostForm(post);
} else {
this.postView.showError('Post not found');
}
} catch (error) {
this.postView.showError(`Failed to load post: ${error.message}`);
}
}
async updatePost(postId, postData) {
try {
const post = await this.postModel.findById(postId);
if (post) {
await post.update(postData);
this.postView.showSuccess('Post updated successfully');
this.loadPosts();
} else {
this.postView.showError('Post not found');
}
} catch (error) {
this.postView.showError(`Failed to update post: ${error.message}`);
}
}
async deletePost(postId) {
try {
const post = await this.postModel.findById(postId);
if (post) {
await post.delete();
this.postView.showSuccess('Post deleted successfully');
this.loadPosts();
} else {
this.postView.showError('Post not found');
}
} catch (error) {
this.postView.showError(`Failed to delete post: ${error.message}`);
}
}
cancelForm() {
this.loadPosts();
}
}
# 4. Application Initialization
// app.js
class App {
constructor() {
this.userController = new UserController();
this.postController = new PostController();
this.init();
}
init() {
// Initialize global controllers
window.userController = this.userController;
window.postController = this.postController;
// Set up navigation
this.setupNavigation();
// Load initial data
this.loadInitialData();
}
setupNavigation() {
const nav = document.getElementById('main-nav');
nav.addEventListener('click', (e) => {
if (e.target.tagName === 'A') {
e.preventDefault();
const href = e.target.getAttribute('href');
this.navigate(href);
}
});
}
navigate(route) {
// Hide all containers
document.querySelectorAll('.container').forEach(container => {
container.style.display = 'none';
});
// Show target container
const targetContainer = document.getElementById(route.replace('#', '') + '-container');
if (targetContainer) {
targetContainer.style.display = 'block';
}
}
loadInitialData() {
// Load users and posts
this.userController.loadUsers();
this.postController.loadPosts();
}
}
// Initialize application
document.addEventListener('DOMContentLoaded', () => {
new App();
});
MVP (Model-View-Presenter) Pattern
MVP Implementation
MVP Pattern Implementation
# MVP Architecture Pattern
# 1. Model Layer (Same as MVC)
// models/User.js - Same as MVC implementation
class User {
constructor(id, name, email) {
this.id = id;
this.name = name;
this.email = email;
this.createdAt = new Date();
}
validate() {
const errors = [];
if (!this.name || this.name.trim().length === 0) {
errors.push('Name is required');
}
if (!this.email || !this.isValidEmail(this.email)) {
errors.push('Valid email is required');
}
return errors;
}
isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
async save() {
const errors = this.validate();
if (errors.length > 0) {
throw new Error(`Validation failed: ${errors.join(', ')}`);
}
return await this.database.save('users', this);
}
async findById(id) {
return await this.database.findById('users', id);
}
async findAll(filters = {}) {
return await this.database.findAll('users', filters);
}
async update(data) {
Object.assign(this, data);
return await this.save();
}
async delete() {
return await this.database.delete('users', this.id);
}
}
# 2. View Interface
// views/IUserView.js
class IUserView {
// Abstract methods that must be implemented
showUsers(users) {
throw new Error('showUsers method must be implemented');
}
showUser(user) {
throw new Error('showUser method must be implemented');
}
showUserForm(user = null) {
throw new Error('showUserForm method must be implemented');
}
showError(message) {
throw new Error('showError method must be implemented');
}
showSuccess(message) {
throw new Error('showSuccess method must be implemented');
}
getUserFormData() {
throw new Error('getUserFormData method must be implemented');
}
clearForm() {
throw new Error('clearForm method must be implemented');
}
}
# 3. View Implementation
// views/UserView.js
class UserView extends IUserView {
constructor() {
super();
this.container = document.getElementById('user-container');
this.presenter = null;
this.init();
}
init() {
// Set up event listeners
this.setupEventListeners();
}
setupEventListeners() {
// Form submission
document.addEventListener('submit', (e) => {
if (e.target.id === 'user-form') {
e.preventDefault();
this.onFormSubmit();
}
});
// Button clicks
document.addEventListener('click', (e) => {
if (e.target.classList.contains('edit-user-btn')) {
const userId = e.target.dataset.userId;
this.onEditUser(userId);
} else if (e.target.classList.contains('delete-user-btn')) {
const userId = e.target.dataset.userId;
this.onDeleteUser(userId);
} else if (e.target.classList.contains('view-user-btn')) {
const userId = e.target.dataset.userId;
this.onViewUser(userId);
} else if (e.target.classList.contains('create-user-btn')) {
this.onCreateUser();
} else if (e.target.classList.contains('cancel-form-btn')) {
this.onCancelForm();
}
});
}
setPresenter(presenter) {
this.presenter = presenter;
}
showUsers(users) {
const usersHTML = users.map(user => `
`).join('');
this.container.innerHTML = `
Users
${usersHTML}
`;
}
showUser(user) {
this.container.innerHTML = `
${this.escapeHtml(user.name)}
`;
}
showUserForm(user = null) {
const isEdit = user !== null;
const formHTML = `
${isEdit ? 'Edit User' : 'Create User'}
`;
this.container.innerHTML = formHTML;
}
showError(message) {
const errorHTML = `
`;
this.container.insertAdjacentHTML('beforebegin', errorHTML);
}
showSuccess(message) {
const successHTML = `
`;
this.container.insertAdjacentHTML('beforebegin', successHTML);
}
getUserFormData() {
const form = document.getElementById('user-form');
if (!form) return null;
const formData = new FormData(form);
return Object.fromEntries(formData);
}
clearForm() {
const form = document.getElementById('user-form');
if (form) {
form.reset();
}
}
// Event handlers
onFormSubmit() {
if (this.presenter) {
this.presenter.onFormSubmit();
}
}
onEditUser(userId) {
if (this.presenter) {
this.presenter.onEditUser(userId);
}
}
onDeleteUser(userId) {
if (this.presenter) {
this.presenter.onDeleteUser(userId);
}
}
onViewUser(userId) {
if (this.presenter) {
this.presenter.onViewUser(userId);
}
}
onCreateUser() {
if (this.presenter) {
this.presenter.onCreateUser();
}
}
onCancelForm() {
if (this.presenter) {
this.presenter.onCancelForm();
}
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
formatDate(date) {
return new Date(date).toLocaleDateString();
}
}
# 4. Presenter Layer
// presenters/UserPresenter.js
class UserPresenter {
constructor(view, model) {
this.view = view;
this.model = model;
this.init();
}
init() {
this.view.setPresenter(this);
this.loadUsers();
}
async loadUsers() {
try {
const users = await this.model.findAll();
this.view.showUsers(users);
} catch (error) {
this.view.showError(`Failed to load users: ${error.message}`);
}
}
async onViewUser(userId) {
try {
const user = await this.model.findById(userId);
if (user) {
this.view.showUser(user);
} else {
this.view.showError('User not found');
}
} catch (error) {
this.view.showError(`Failed to load user: ${error.message}`);
}
}
onCreateUser() {
this.view.showUserForm();
}
async onEditUser(userId) {
try {
const user = await this.model.findById(userId);
if (user) {
this.view.showUserForm(user);
} else {
this.view.showError('User not found');
}
} catch (error) {
this.view.showError(`Failed to load user: ${error.message}`);
}
}
async onFormSubmit() {
const userData = this.view.getUserFormData();
if (!userData) {
this.view.showError('No form data available');
return;
}
try {
// Check if we're editing or creating
const isEdit = userData.id && userData.id !== '';
if (isEdit) {
await this.updateUser(userData.id, userData);
} else {
await this.createUser(userData);
}
} catch (error) {
this.view.showError(`Form submission failed: ${error.message}`);
}
}
async createUser(userData) {
try {
const user = new User(null, userData.name, userData.email);
await user.save();
this.view.showSuccess('User created successfully');
this.view.clearForm();
this.loadUsers();
} catch (error) {
this.view.showError(`Failed to create user: ${error.message}`);
}
}
async updateUser(userId, userData) {
try {
const user = await this.model.findById(userId);
if (user) {
await user.update(userData);
this.view.showSuccess('User updated successfully');
this.loadUsers();
} else {
this.view.showError('User not found');
}
} catch (error) {
this.view.showError(`Failed to update user: ${error.message}`);
}
}
async onDeleteUser(userId) {
if (confirm('Are you sure you want to delete this user?')) {
try {
const user = await this.model.findById(userId);
if (user) {
await user.delete();
this.view.showSuccess('User deleted successfully');
this.loadUsers();
} else {
this.view.showError('User not found');
}
} catch (error) {
this.view.showError(`Failed to delete user: ${error.message}`);
}
}
}
onCancelForm() {
this.loadUsers();
}
}
# 5. Application Initialization
// app.js
class App {
constructor() {
this.init();
}
init() {
// Initialize MVP components
const userModel = new User();
const userView = new UserView();
const userPresenter = new UserPresenter(userView, userModel);
// Store references
this.userPresenter = userPresenter;
}
}
// Initialize application
document.addEventListener('DOMContentLoaded', () => {
new App();
});
MVVM (Model-View-ViewModel) Pattern
MVVM Implementation
MVVM Pattern Implementation
# MVVM Architecture Pattern
# 1. Model Layer (Same as MVC/MVP)
// models/User.js - Same as previous implementations
class User {
constructor(id, name, email) {
this.id = id;
this.name = name;
this.email = email;
this.createdAt = new Date();
}
validate() {
const errors = [];
if (!this.name || this.name.trim().length === 0) {
errors.push('Name is required');
}
if (!this.email || !this.isValidEmail(this.email)) {
errors.push('Valid email is required');
}
return errors;
}
isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
async save() {
const errors = this.validate();
if (errors.length > 0) {
throw new Error(`Validation failed: ${errors.join(', ')}`);
}
return await this.database.save('users', this);
}
async findById(id) {
return await this.database.findById('users', id);
}
async findAll(filters = {}) {
return await this.database.findAll('users', filters);
}
async update(data) {
Object.assign(this, data);
return await this.save();
}
async delete() {
return await this.database.delete('users', this.id);
}
}
# 2. ViewModel Layer
// viewmodels/UserViewModel.js
class UserViewModel {
constructor() {
this.users = [];
this.selectedUser = null;
this.isLoading = false;
this.error = null;
this.success = null;
this.formData = {
name: '',
email: ''
};
this.isEditMode = false;
// Bind methods to maintain context
this.loadUsers = this.loadUsers.bind(this);
this.selectUser = this.selectUser.bind(this);
this.createUser = this.createUser.bind(this);
this.updateUser = this.updateUser.bind(this);
this.deleteUser = this.deleteUser.bind(this);
this.setFormData = this.setFormData.bind(this);
this.clearForm = this.clearForm.bind(this);
}
async loadUsers() {
this.isLoading = true;
this.error = null;
try {
const userModel = new User();
this.users = await userModel.findAll();
} catch (error) {
this.error = `Failed to load users: ${error.message}`;
} finally {
this.isLoading = false;
}
}
async selectUser(userId) {
try {
const userModel = new User();
this.selectedUser = await userModel.findById(userId);
this.setFormData(this.selectedUser);
this.isEditMode = true;
} catch (error) {
this.error = `Failed to load user: ${error.message}`;
}
}
async createUser() {
this.isLoading = true;
this.error = null;
this.success = null;
try {
const userModel = new User(null, this.formData.name, this.formData.email);
await userModel.save();
this.success = 'User created successfully';
this.clearForm();
await this.loadUsers();
} catch (error) {
this.error = `Failed to create user: ${error.message}`;
} finally {
this.isLoading = false;
}
}
async updateUser() {
if (!this.selectedUser) {
this.error = 'No user selected for update';
return;
}
this.isLoading = true;
this.error = null;
this.success = null;
try {
await this.selectedUser.update(this.formData);
this.success = 'User updated successfully';
this.clearForm();
await this.loadUsers();
} catch (error) {
this.error = `Failed to update user: ${error.message}`;
} finally {
this.isLoading = false;
}
}
async deleteUser(userId) {
if (!confirm('Are you sure you want to delete this user?')) {
return;
}
this.isLoading = true;
this.error = null;
this.success = null;
try {
const userModel = new User();
const user = await userModel.findById(userId);
if (user) {
await user.delete();
this.success = 'User deleted successfully';
await this.loadUsers();
} else {
this.error = 'User not found';
}
} catch (error) {
this.error = `Failed to delete user: ${error.message}`;
} finally {
this.isLoading = false;
}
}
setFormData(user) {
if (user) {
this.formData = {
name: user.name || '',
email: user.email || ''
};
} else {
this.clearForm();
}
}
clearForm() {
this.formData = {
name: '',
email: ''
};
this.selectedUser = null;
this.isEditMode = false;
}
startCreateMode() {
this.clearForm();
this.isEditMode = false;
}
// Computed properties
get hasUsers() {
return this.users.length > 0;
}
get canSubmit() {
return this.formData.name.trim() !== '' &&
this.formData.email.trim() !== '' &&
!this.isLoading;
}
get submitButtonText() {
return this.isEditMode ? 'Update User' : 'Create User';
}
}
# 3. View Layer with Data Binding
// views/UserView.js
class UserView {
constructor(viewModel) {
this.viewModel = viewModel;
this.container = document.getElementById('user-container');
this.init();
}
init() {
this.setupEventListeners();
this.bindViewModel();
this.render();
}
setupEventListeners() {
// Form submission
document.addEventListener('submit', (e) => {
if (e.target.id === 'user-form') {
e.preventDefault();
this.handleFormSubmit();
}
});
// Button clicks
document.addEventListener('click', (e) => {
if (e.target.classList.contains('view-user-btn')) {
const userId = e.target.dataset.userId;
this.viewModel.selectUser(userId);
} else if (e.target.classList.contains('edit-user-btn')) {
const userId = e.target.dataset.userId;
this.viewModel.selectUser(userId);
} else if (e.target.classList.contains('delete-user-btn')) {
const userId = e.target.dataset.userId;
this.viewModel.deleteUser(userId);
} else if (e.target.classList.contains('create-user-btn')) {
this.viewModel.startCreateMode();
} else if (e.target.classList.contains('cancel-form-btn')) {
this.viewModel.clearForm();
}
});
// Form input changes
document.addEventListener('input', (e) => {
if (e.target.name === 'name') {
this.viewModel.formData.name = e.target.value;
} else if (e.target.name === 'email') {
this.viewModel.formData.email = e.target.value;
}
});
}
bindViewModel() {
// Create reactive bindings
this.createReactiveBinding('users', () => this.renderUserList());
this.createReactiveBinding('selectedUser', () => this.renderUserDetail());
this.createReactiveBinding('formData', () => this.updateForm());
this.createReactiveBinding('isLoading', () => this.updateLoadingState());
this.createReactiveBinding('error', () => this.showError());
this.createReactiveBinding('success', () => this.showSuccess());
this.createReactiveBinding('isEditMode', () => this.updateFormMode());
}
createReactiveBinding(property, callback) {
let value = this.viewModel[property];
Object.defineProperty(this.viewModel, property, {
get() {
return value;
},
set(newValue) {
value = newValue;
callback();
}
});
}
render() {
this.renderUserList();
this.renderUserForm();
this.updateLoadingState();
}
renderUserList() {
const usersListHTML = `
Users
${this.viewModel.hasUsers ?
this.viewModel.users.map(user => `
`).join('') :
'No users found
'
}
`;
const listContainer = this.container.querySelector('.users-list') ||
document.createElement('div');
listContainer.className = 'users-list';
listContainer.innerHTML = usersListHTML;
if (!this.container.querySelector('.users-list')) {
this.container.appendChild(listContainer);
}
}
renderUserForm() {
const formHTML = `
${this.viewModel.isEditMode ? 'Edit User' : 'Create User'}
`;
const formContainer = this.container.querySelector('.user-form-container') ||
document.createElement('div');
formContainer.className = 'user-form-container';
formContainer.innerHTML = formHTML;
if (!this.container.querySelector('.user-form-container')) {
this.container.appendChild(formContainer);
}
}
renderUserDetail() {
if (!this.viewModel.selectedUser) return;
const detailHTML = `
${this.escapeHtml(this.viewModel.selectedUser.name)}
`;
const detailContainer = this.container.querySelector('.user-detail') ||
document.createElement('div');
detailContainer.className = 'user-detail';
detailContainer.innerHTML = detailHTML;
if (!this.container.querySelector('.user-detail')) {
this.container.appendChild(detailContainer);
}
}
updateForm() {
const nameInput = document.getElementById('name');
const emailInput = document.getElementById('email');
if (nameInput) {
nameInput.value = this.viewModel.formData.name;
}
if (emailInput) {
emailInput.value = this.viewModel.formData.email;
}
}
updateFormMode() {
const formTitle = this.container.querySelector('.user-form-container h2');
if (formTitle) {
formTitle.textContent = this.viewModel.isEditMode ? 'Edit User' : 'Create User';
}
const submitButton = this.container.querySelector('#user-form button[type="submit"]');
if (submitButton) {
submitButton.textContent = this.viewModel.submitButtonText;
}
}
updateLoadingState() {
const buttons = this.container.querySelectorAll('button');
buttons.forEach(button => {
button.disabled = this.viewModel.isLoading;
});
if (this.viewModel.isLoading) {
this.container.classList.add('loading');
} else {
this.container.classList.remove('loading');
}
}
showError() {
if (!this.viewModel.error) return;
const errorHTML = `
`;
this.container.insertAdjacentHTML('beforebegin', errorHTML);
// Clear error after showing
setTimeout(() => {
this.viewModel.error = null;
}, 5000);
}
showSuccess() {
if (!this.viewModel.success) return;
const successHTML = `
`;
this.container.insertAdjacentHTML('beforebegin', successHTML);
// Clear success after showing
setTimeout(() => {
this.viewModel.success = null;
}, 3000);
}
handleFormSubmit() {
if (this.viewModel.isEditMode) {
this.viewModel.updateUser();
} else {
this.viewModel.createUser();
}
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
formatDate(date) {
return new Date(date).toLocaleDateString();
}
}
# 4. Application Initialization
// app.js
class App {
constructor() {
this.init();
}
init() {
// Initialize MVVM components
const userViewModel = new UserViewModel();
const userView = new UserView(userViewModel);
// Store references
this.userViewModel = userViewModel;
this.userView = userView;
// Load initial data
this.userViewModel.loadUsers();
}
}
// Initialize application
document.addEventListener('DOMContentLoaded', () => {
new App();
});
Repository Pattern
Repository Pattern Implementation
Repository Pattern Implementation
# Repository Pattern Implementation
# 1. Repository Interface
// repositories/IRepository.js
class IRepository {
async findById(id) {
throw new Error('findById method must be implemented');
}
async findAll(filters = {}) {
throw new Error('findAll method must be implemented');
}
async create(data) {
throw new Error('create method must be implemented');
}
async update(id, data) {
throw new Error('update method must be implemented');
}
async delete(id) {
throw new Error('delete method must be implemented');
}
async exists(id) {
throw new Error('exists method must be implemented');
}
async count(filters = {}) {
throw new Error('count method must be implemented');
}
}
# 2. User Repository Implementation
// repositories/UserRepository.js
class UserRepository extends IRepository {
constructor(database) {
super();
this.database = database;
this.tableName = 'users';
}
async findById(id) {
try {
const result = await this.database.query(
`SELECT * FROM ${this.tableName} WHERE id = ?`,
[id]
);
if (result.length === 0) {
return null;
}
return this.mapToEntity(result[0]);
} catch (error) {
throw new Error(`Failed to find user by ID: ${error.message}`);
}
}
async findAll(filters = {}) {
try {
let query = `SELECT * FROM ${this.tableName}`;
const params = [];
if (Object.keys(filters).length > 0) {
const conditions = [];
if (filters.name) {
conditions.push('name LIKE ?');
params.push(`%${filters.name}%`);
}
if (filters.email) {
conditions.push('email LIKE ?');
params.push(`%${filters.email}%`);
}
if (filters.isActive !== undefined) {
conditions.push('is_active = ?');
params.push(filters.isActive);
}
if (conditions.length > 0) {
query += ` WHERE ${conditions.join(' AND ')}`;
}
}
if (filters.orderBy) {
query += ` ORDER BY ${filters.orderBy}`;
if (filters.orderDirection) {
query += ` ${filters.orderDirection}`;
}
}
if (filters.limit) {
query += ` LIMIT ${filters.limit}`;
if (filters.offset) {
query += ` OFFSET ${filters.offset}`;
}
}
const result = await this.database.query(query, params);
return result.map(row => this.mapToEntity(row));
} catch (error) {
throw new Error(`Failed to find users: ${error.message}`);
}
}
async create(data) {
try {
const user = new User(null, data.name, data.email);
const errors = user.validate();
if (errors.length > 0) {
throw new Error(`Validation failed: ${errors.join(', ')}`);
}
const result = await this.database.query(
`INSERT INTO ${this.tableName} (name, email, created_at) VALUES (?, ?, ?)`,
[data.name, data.email, new Date()]
);
return await this.findById(result.insertId);
} catch (error) {
throw new Error(`Failed to create user: ${error.message}`);
}
}
async update(id, data) {
try {
const existingUser = await this.findById(id);
if (!existingUser) {
throw new Error('User not found');
}
const updatedData = { ...existingUser, ...data };
const user = new User(id, updatedData.name, updatedData.email);
const errors = user.validate();
if (errors.length > 0) {
throw new Error(`Validation failed: ${errors.join(', ')}`);
}
await this.database.query(
`UPDATE ${this.tableName} SET name = ?, email = ?, updated_at = ? WHERE id = ?`,
[data.name, data.email, new Date(), id]
);
return await this.findById(id);
} catch (error) {
throw new Error(`Failed to update user: ${error.message}`);
}
}
async delete(id) {
try {
const user = await this.findById(id);
if (!user) {
throw new Error('User not found');
}
await this.database.query(
`DELETE FROM ${this.tableName} WHERE id = ?`,
[id]
);
return true;
} catch (error) {
throw new Error(`Failed to delete user: ${error.message}`);
}
}
async exists(id) {
try {
const result = await this.database.query(
`SELECT COUNT(*) as count FROM ${this.tableName} WHERE id = ?`,
[id]
);
return result[0].count > 0;
} catch (error) {
throw new Error(`Failed to check user existence: ${error.message}`);
}
}
async count(filters = {}) {
try {
let query = `SELECT COUNT(*) as count FROM ${this.tableName}`;
const params = [];
if (Object.keys(filters).length > 0) {
const conditions = [];
if (filters.name) {
conditions.push('name LIKE ?');
params.push(`%${filters.name}%`);
}
if (filters.email) {
conditions.push('email LIKE ?');
params.push(`%${filters.email}%`);
}
if (filters.isActive !== undefined) {
conditions.push('is_active = ?');
params.push(filters.isActive);
}
if (conditions.length > 0) {
query += ` WHERE ${conditions.join(' AND ')}`;
}
}
const result = await this.database.query(query, params);
return result[0].count;
} catch (error) {
throw new Error(`Failed to count users: ${error.message}`);
}
}
mapToEntity(row) {
return new User(
row.id,
row.name,
row.email,
row.created_at,
row.updated_at
);
}
}
# 3. Post Repository Implementation
// repositories/PostRepository.js
class PostRepository extends IRepository {
constructor(database) {
super();
this.database = database;
this.tableName = 'posts';
}
async findById(id) {
try {
const result = await this.database.query(
`SELECT * FROM ${this.tableName} WHERE id = ?`,
[id]
);
if (result.length === 0) {
return null;
}
return this.mapToEntity(result[0]);
} catch (error) {
throw new Error(`Failed to find post by ID: ${error.message}`);
}
}
async findAll(filters = {}) {
try {
let query = `SELECT * FROM ${this.tableName}`;
const params = [];
if (Object.keys(filters).length > 0) {
const conditions = [];
if (filters.title) {
conditions.push('title LIKE ?');
params.push(`%${filters.title}%`);
}
if (filters.content) {
conditions.push('content LIKE ?');
params.push(`%${filters.content}%`);
}
if (filters.authorId) {
conditions.push('author_id = ?');
params.push(filters.authorId);
}
if (filters.isPublished !== undefined) {
conditions.push('is_published = ?');
params.push(filters.isPublished);
}
if (conditions.length > 0) {
query += ` WHERE ${conditions.join(' AND ')}`;
}
}
if (filters.orderBy) {
query += ` ORDER BY ${filters.orderBy}`;
if (filters.orderDirection) {
query += ` ${filters.orderDirection}`;
}
}
if (filters.limit) {
query += ` LIMIT ${filters.limit}`;
if (filters.offset) {
query += ` OFFSET ${filters.offset}`;
}
}
const result = await this.database.query(query, params);
return result.map(row => this.mapToEntity(row));
} catch (error) {
throw new Error(`Failed to find posts: ${error.message}`);
}
}
async create(data) {
try {
const post = new Post(null, data.title, data.content, data.authorId);
const errors = post.validate();
if (errors.length > 0) {
throw new Error(`Validation failed: ${errors.join(', ')}`);
}
const result = await this.database.query(
`INSERT INTO ${this.tableName} (title, content, author_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?)`,
[data.title, data.content, data.authorId, new Date(), new Date()]
);
return await this.findById(result.insertId);
} catch (error) {
throw new Error(`Failed to create post: ${error.message}`);
}
}
async update(id, data) {
try {
const existingPost = await this.findById(id);
if (!existingPost) {
throw new Error('Post not found');
}
const updatedData = { ...existingPost, ...data };
const post = new Post(id, updatedData.title, updatedData.content, updatedData.authorId);
const errors = post.validate();
if (errors.length > 0) {
throw new Error(`Validation failed: ${errors.join(', ')}`);
}
await this.database.query(
`UPDATE ${this.tableName} SET title = ?, content = ?, updated_at = ? WHERE id = ?`,
[data.title, data.content, new Date(), id]
);
return await this.findById(id);
} catch (error) {
throw new Error(`Failed to update post: ${error.message}`);
}
}
async delete(id) {
try {
const post = await this.findById(id);
if (!post) {
throw new Error('Post not found');
}
await this.database.query(
`DELETE FROM ${this.tableName} WHERE id = ?`,
[id]
);
return true;
} catch (error) {
throw new Error(`Failed to delete post: ${error.message}`);
}
}
async exists(id) {
try {
const result = await this.database.query(
`SELECT COUNT(*) as count FROM ${this.tableName} WHERE id = ?`,
[id]
);
return result[0].count > 0;
} catch (error) {
throw new Error(`Failed to check post existence: ${error.message}`);
}
}
async count(filters = {}) {
try {
let query = `SELECT COUNT(*) as count FROM ${this.tableName}`;
const params = [];
if (Object.keys(filters).length > 0) {
const conditions = [];
if (filters.title) {
conditions.push('title LIKE ?');
params.push(`%${filters.title}%`);
}
if (filters.content) {
conditions.push('content LIKE ?');
params.push(`%${filters.content}%`);
}
if (filters.authorId) {
conditions.push('author_id = ?');
params.push(filters.authorId);
}
if (filters.isPublished !== undefined) {
conditions.push('is_published = ?');
params.push(filters.isPublished);
}
if (conditions.length > 0) {
query += ` WHERE ${conditions.join(' AND ')}`;
}
}
const result = await this.database.query(query, params);
return result[0].count;
} catch (error) {
throw new Error(`Failed to count posts: ${error.message}`);
}
}
mapToEntity(row) {
return new Post(
row.id,
row.title,
row.content,
row.author_id,
row.created_at,
row.updated_at
);
}
}
# 4. Service Layer
// services/UserService.js
class UserService {
constructor(userRepository) {
this.userRepository = userRepository;
}
async getAllUsers(filters = {}) {
try {
return await this.userRepository.findAll(filters);
} catch (error) {
throw new Error(`Failed to get users: ${error.message}`);
}
}
async getUserById(id) {
try {
const user = await this.userRepository.findById(id);
if (!user) {
throw new Error('User not found');
}
return user;
} catch (error) {
throw new Error(`Failed to get user: ${error.message}`);
}
}
async createUser(userData) {
try {
// Additional business logic
const existingUser = await this.userRepository.findAll({ email: userData.email });
if (existingUser.length > 0) {
throw new Error('User with this email already exists');
}
return await this.userRepository.create(userData);
} catch (error) {
throw new Error(`Failed to create user: ${error.message}`);
}
}
async updateUser(id, userData) {
try {
const user = await this.userRepository.findById(id);
if (!user) {
throw new Error('User not found');
}
// Additional business logic
if (userData.email && userData.email !== user.email) {
const existingUser = await this.userRepository.findAll({ email: userData.email });
if (existingUser.length > 0) {
throw new Error('User with this email already exists');
}
}
return await this.userRepository.update(id, userData);
} catch (error) {
throw new Error(`Failed to update user: ${error.message}`);
}
}
async deleteUser(id) {
try {
const user = await this.userRepository.findById(id);
if (!user) {
throw new Error('User not found');
}
return await this.userRepository.delete(id);
} catch (error) {
throw new Error(`Failed to delete user: ${error.message}`);
}
}
async getUserCount(filters = {}) {
try {
return await this.userRepository.count(filters);
} catch (error) {
throw new Error(`Failed to get user count: ${error.message}`);
}
}
}
# 5. Application Initialization
// app.js
class App {
constructor() {
this.init();
}
init() {
// Initialize database connection
const database = new Database();
// Initialize repositories
const userRepository = new UserRepository(database);
const postRepository = new PostRepository(database);
// Initialize services
const userService = new UserService(userRepository);
const postService = new PostService(postRepository);
// Initialize controllers
const userController = new UserController(userService);
const postController = new PostController(postService);
// Store references
this.userController = userController;
this.postController = postController;
}
}
// Initialize application
document.addEventListener('DOMContentLoaded', () => {
new App();
});
Architecture Patterns Comparison
Pattern Selection Guide
MVC Pattern
- Simple and widely understood
- Good for small to medium applications
- Direct communication between components
- Easy to implement and debug
- Best for: Traditional web applications
MVP Pattern
- Better separation of concerns
- Easier testing with mock views
- View is passive, presenter handles logic
- Good for complex UI interactions
- Best for: Desktop applications
MVVM Pattern
- Data binding and reactive updates
- Excellent for modern frameworks
- Automatic UI updates
- Complex but powerful
- Best for: Modern web applications
Repository Pattern
- Abstracts data access
- Easier testing and mocking
- Database independence
- Centralized data logic
- Best for: Data-heavy applications
Summary
Code architecture patterns implementation involves several key areas:
- MVC Pattern: Model-View-Controller for simple applications
- MVP Pattern: Model-View-Presenter for better testing
- MVVM Pattern: Model-View-ViewModel for reactive applications
- Repository Pattern: Data access abstraction for maintainability
Need More Help?
Struggling with architecture implementation or need help choosing the right pattern? Our architecture experts can help you implement effective code organization strategies.
Get Architecture Help