JavaScriptã§ãã¡ã€ã³é§åèšèšããã¹ã¿ãŒããŸããããã¢ãžã¥ãŒã«ãšã³ãã£ãã£ãã¿ãŒã³ãåŠãã§ãå ç¢ãªãã¡ã€ã³ãªããžã§ã¯ãã¢ãã«ã§ãã¹ã±ãŒã©ãã«ã§ãã¹ãå¯èœããã€ä¿å®å¯èœãªã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ããŸãã
JavaScriptã¢ãžã¥ãŒã«ãšã³ãã£ãã£ãã¿ãŒã³ïŒãã¡ã€ã³ãªããžã§ã¯ãã¢ããªã³ã°ã®è©³çް
ãœãããŠã§ã¢éçºã®äžçãç¹ã«ãã€ãããã¯ã§åžžã«é²åããJavaScriptãšã³ã·ã¹ãã ã§ã¯ãã¹ããŒãããã¬ãŒã ã¯ãŒã¯ãæ©èœãåªå ããããšããããããŸããè€éãªãŠãŒã¶ãŒã€ã³ã¿ãŒãã§ãŒã¹ãæ§ç¯ããç¡æ°ã®APIã«æ¥ç¶ããç®ãŸããããéãã§ã¢ããªã±ãŒã·ã§ã³ããããã€ããŸãããããããã®æ¥ãè¶³ã®äžã§ãã¢ããªã±ãŒã·ã§ã³ã®äžæ žã§ããããžãã¹ãã¡ã€ã³ã軜èŠããããšããããŸããããã«ãããããžãã¹ããžãã¯ãæ£åšããããŒã¿ãæ§é åãããŠããããç°¡åãªå€æŽãäºæãã¬ãã°ã®é£éãåŒãèµ·ãããæ³¥ã®å€§ããªå¡ïŒBig Ball of MudïŒããšåŒã°ããã·ã¹ãã ã«ã€ãªããå¯èœæ§ããããŸãã
ããã§ãã¡ã€ã³ãªããžã§ã¯ãã¢ããªã³ã°ãç»å ŽããŸããããã¯ãäœæ¥ããŠããåé¡é åã®è±å¯ã§è¡šçŸåè±ããªã¢ãã«ãäœæãããã©ã¯ãã£ã¹ã§ãããããŠJavaScriptã§ã¯ãã¢ãžã¥ãŒã«ãšã³ãã£ãã£ãã¿ãŒã³ã¯ããããå®çŸããããã®åŒ·åã§ãšã¬ã¬ã³ãã§ããã¬ãŒã ã¯ãŒã¯ã«äŸåããªãæ¹æ³ã§ãããã®å æ¬çãªã¬ã€ãã§ã¯ããã®ãã¿ãŒã³ã®çè«ãå®è·µãå©ç¹ã«ã€ããŠèª¬æããããå ç¢ã§ã¹ã±ãŒã©ãã«ã§ãä¿å®å¯èœãªã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ã§ããããã«ããŸãã
ãã¡ã€ã³ãªããžã§ã¯ãã¢ããªã³ã°ãšã¯ïŒ
ãã¿ãŒã³èªäœã«å ¥ãåã«ãçšèªãæç¢ºã«ããŸãããããã®æŠå¿µããã©ãŠã¶ã®Document Object ModelïŒDOMïŒãšåºå¥ããããšãéèŠã§ãã
- ãã¡ã€ã³ïŒãœãããŠã§ã¢ã§ã¯ãããã¡ã€ã³ãã¯ããŠãŒã¶ãŒã®ããžãã¹ãå±ããç¹å®ã®äž»é¡é åã§ããeã³ããŒã¹ã¢ããªã±ãŒã·ã§ã³ã®å Žåããã¡ã€ã³ã«ã¯ã補åãé¡§å®¢ãæ³šæãæ¯æããªã©ã®æŠå¿µãå«ãŸããŸãããœãŒã·ã£ã«ã¡ãã£ã¢ãã©ãããã©ãŒã ã®å ŽåããŠãŒã¶ãŒãæçš¿ãã³ã¡ã³ãããããïŒãå«ãŸããŸãã
- ãã¡ã€ã³ãªããžã§ã¯ãã¢ããªã³ã°ïŒããã¯ããšã³ãã£ãã£ããã®åäœãããã³ããžãã¹ãã¡ã€ã³å ã®é¢ä¿ã衚ããœãããŠã§ã¢ã¢ãã«ãäœæããããã»ã¹ã§ããããã¯ãçŸå®äžçã®æŠå¿µãã³ãŒãã«å€æããããšã§ãã
åªãããã¡ã€ã³ã¢ãã«ã¯ãåãªãããŒã¿ã³ã³ããã®ã³ã¬ã¯ã·ã§ã³ã§ã¯ãããŸãããããã¯ãããžãã¹ã«ãŒã«ã®çãã衚çŸã§ããOrderãªããžã§ã¯ãã¯ãã¢ã€ãã ã®ãªã¹ããä¿æããã ãã§ãªããåèšãèšç®ããæ¹æ³ãæ°ããã¢ã€ãã ã远å ããæ¹æ³ãããã³ãã£ã³ã»ã«ã§ãããã©ãããç¥ã£ãŠããå¿ èŠããããŸããããŒã¿ãšåäœã®ãã®ã«ãã»ã«åã¯ãå埩åã®ããã¢ããªã±ãŒã·ã§ã³ã³ã¢ãæ§ç¯ããããã®éµã§ãã
äžè¬çãªåé¡ïŒãã¢ãã«ãã¬ã€ã€ãŒã®ç¡ç§©åº
å€ãã®JavaScriptã¢ããªã±ãŒã·ã§ã³ãç¹ã«ææ©çã«æé·ããã¢ããªã±ãŒã·ã§ã³ã§ã¯ããã¢ãã«ãã¬ã€ã€ãŒã¯åŸåãã«ãããããšããããããŸããç§ãã¡ã¯é »ç¹ã«ãã®ã¢ã³ããã¿ãŒã³ãç®ã«ããŸãã
// Somewhere in an API controller or service...
async function createUser(req, res) {
const { email, password, firstName, lastName } = req.body;
// Business logic and validation is scattered here
if (!email || !email.includes('@')) {
return res.status(400).send({ error: 'A valid email is required.' });
}
if (!password || password.length < 8) {
return res.status(400).send({ error: 'Password must be at least 8 characters.' });
}
const user = {
email: email.toLowerCase(),
password: await hashPassword(password), // Some utility function
fullName: `${firstName} ${lastName}`, // Logic for derived data is here
createdAt: new Date()
};
// Now, what is `user`? It's just a plain object.
// Nothing stops another developer from doing this later:
// user.email = 'an-invalid-email';
// user.password = 'short';
await db.users.insert(user);
res.status(201).send(user);
}
ãã®ã¢ãããŒãã«ã¯ãããã€ãã®é倧ãªåé¡ããããŸãã
- å¯äžã®çå®ã®æºããªãïŒæå¹ãªããŠãŒã¶ãŒããæ§æããã«ãŒã«ã¯ããã®1ã€ã®ã³ã³ãããŒã©ãŒå ã§å®çŸ©ãããŠããŸããã·ã¹ãã ã®å¥ã®éšåããŠãŒã¶ãŒãäœæããå¿ èŠãããå Žåã¯ã©ããªããŸããïŒããžãã¯ãã³ããŒïŒããŒã¹ãããŸããïŒããã¯ãäžæŽåãšãã°ã«ã€ãªãããŸãã
- è²§è¡ãã¡ã€ã³ã¢ãã«ïŒ `user`ãªããžã§ã¯ãã¯ãåãªãããã ããªããŒã¿ã®å¡ã§ããããã«ã¯è¡åãèªå·±èªèããããŸãããããã«å¯ŸããŠæäœãããã¹ãŠã®ããžãã¯ã¯ãå€éšã«ååšããŸãã
- äœãåé床ïŒãŠãŒã¶ãŒã®ãã«ããŒã ãäœæããããã®ããžãã¯ã¯ãAPIãªã¯ãšã¹ã/ã¬ã¹ãã³ã¹ã®åŠçãšãã¹ã¯ãŒãã®ããã·ã¥åãšæ··ãã£ãŠããŸãã
- ãã¹ããå°é£ïŒãŠãŒã¶ãŒäœæããžãã¯ããã¹ãããã«ã¯ãHTTPãªã¯ãšã¹ããšã¬ã¹ãã³ã¹ãããŒã¿ããŒã¹ãããã³ããã·ã¥é¢æ°ãã¢ãã¯ããå¿ èŠããããŸããåé¢ããŠããŠãŒã¶ãŒãã®æŠå¿µããã¹ãããããšã¯ã§ããŸããã
- æé»ã®å¥çŽïŒã¢ããªã±ãŒã·ã§ã³ã®æ®ãã®éšåã¯ããŠãŒã¶ãŒã衚ããªããžã§ã¯ãã«ã¯ç¹å®ã®åœ¢ç¶ãããããã®ããŒã¿ãæå¹ã§ãããšãæ³å®ãããå¿ èŠããããŸããä¿èšŒã¯ãããŸããã
解決çïŒJavaScriptã¢ãžã¥ãŒã«ãšã³ãã£ãã£ãã¿ãŒã³
ã¢ãžã¥ãŒã«ãšã³ãã£ãã£ãã¿ãŒã³ã¯ãæšæºã®JavaScriptã¢ãžã¥ãŒã«ïŒ1ã€ã®ãã¡ã€ã«ïŒã䜿çšããŠãåäžã®ãã¡ã€ã³æŠå¿µã«é¢ãããã¹ãŠãå®çŸ©ããããšã«ããããããã®åé¡ã«å¯ŸåŠããŸãããã®ã¢ãžã¥ãŒã«ã¯ããã®ãšã³ãã£ãã£ã®ä¿¡é Œã§ããå¯äžã®æ å ±æºã«ãªããŸãã
ã¢ãžã¥ãŒã«ãšã³ãã£ãã£ã¯éåžžããã¡ã¯ããªãŒé¢æ°ãå ¬éããŸãããã®é¢æ°ã¯ããšã³ãã£ãã£ã®æå¹ãªã€ã³ã¹ã¿ã³ã¹ãäœæãã圹å²ãæ ããŸãããããè¿ããªããžã§ã¯ãã¯ãåãªãããŒã¿ã§ã¯ãããŸãããããã¯ãç¬èªã®ããŒã¿ãæ€èšŒãããã³ããžãã¹ããžãã¯ãã«ãã»ã«åããè±å¯ãªãã¡ã€ã³ãªããžã§ã¯ãã§ãã
ã¢ãžã¥ãŒã«ãšã³ãã£ãã£ã®äž»ãªç¹åŸŽ
- ã«ãã»ã«åïŒããŒã¿ãšããã®ããŒã¿ãæäœãã颿°ãäžç·ã«ãã³ãã«ããŸãã
- å¢çã§ã®æ€èšŒïŒç¡å¹ãªãšã³ãã£ãã£ãäœæããããšãäžå¯èœã§ããããšãä¿èšŒããŸããããã¯èªèº«ã®ç¶æ ãä¿è·ããŸãã
- æç¢ºãªAPIïŒãšã³ãã£ãã£ãšå¯Ÿè©±ããããã®æç¢ºã§æå³çãªé¢æ°ã»ããïŒãããªãã¯APIïŒãå ¬éããå éšå®è£ ã®è©³çްãé衚瀺ã«ããŸãã
- äžå€æ§ïŒå€ãã®å Žåãå¶çºçãªç¶æ ã®å€åãé²ããäºæž¬å¯èœãªåäœãä¿èšŒããããã«ãäžå€ãŸãã¯èªã¿åãå°çšã®ãªããžã§ã¯ããçæããŸãã
- ç§»æ€æ§ïŒExpressãReactãªã©ã®ãã¬ãŒã ã¯ãŒã¯ããããŒã¿ããŒã¹ãAPIãªã©ã®å€éšã·ã¹ãã ãžã®äŸåé¢ä¿ã¯ãããŸãããããã¯çŽç²ãªããžãã¹ããžãã¯ã§ãã
ã¢ãžã¥ãŒã«ãšã³ãã£ãã£ã®ã³ã¢ã³ã³ããŒãã³ã
ãã®ãã¿ãŒã³ã䜿çšããŠã`User`ã®æŠå¿µãåæ§ç¯ããŸãããããã¡ã€ã«`user.js`ïŒãŸãã¯TypeScriptãŠãŒã¶ãŒã®å Žåã¯`user.ts`ïŒãäœæããã¹ããããã€ã¹ãããã§æ§ç¯ããŸãã
1. ãã¡ã¯ããªãŒé¢æ°ïŒãªããžã§ã¯ãã³ã³ã¹ãã©ã¯ã¿ãŒ
ã¯ã©ã¹ã®ä»£ããã«ããã¡ã¯ããªãŒé¢æ°ïŒäŸïŒ`buildUser`ïŒã䜿çšããŸãããã¡ã¯ããªãŒã¯åªããæè»æ§ãæäŸãã`this`ããŒã¯ãŒããšã®æ Œéãåé¿ããJavaScriptã§ãã©ã€ããŒããªç¶æ ãšã«ãã»ã«åãããèªç¶ã«ããŸãã
ç§ãã¡ã®ç®æšã¯ãçã®ããŒã¿ãåãåããé©åã«åœ¢æãããä¿¡é Œã§ããUserãªããžã§ã¯ããè¿ã颿°ãäœæããããšã§ãã
// file: /domain/user.js
export default function buildMakeUser() {
// This inner function is the actual factory.
// It has access to any dependencies passed to buildMakeUser, if needed.
return function makeUser({
id = generateId(), // Let's assume a function to generate a unique ID
firstName,
lastName,
email,
passwordHash,
createdAt = new Date()
}) {
// ... validation and logic will go here ...
const user = {
getId: () => id,
getFirstName: () => firstName,
getLastName: () => lastName,
getEmail: () => email,
getPasswordHash: () => passwordHash,
getCreatedAt: () => createdAt
};
// Using Object.freeze to make the object immutable.
return Object.freeze(user);
}
}
ããã§ããã€ãã®ããšã«æ³šç®ããŠãã ããã颿°ãè¿ã颿°ïŒé«é颿°ïŒã䜿çšããŠããŸããããã¯ããšã³ãã£ãã£ãç¹å®ã®å®è£ ã«çµåããããšãªããäžæã®IDãžã§ãã¬ãŒã¿ãŒãããªããŒã¿ãŒã©ã€ãã©ãªãªã©ã®äŸåé¢ä¿ãæ³šå ¥ããããã®åŒ·åãªãã¿ãŒã³ã§ããä»ã®ãšããããããã·ã³ãã«ã«ä¿ã¡ãŸãã
2. ããŒã¿æ€èšŒïŒã²ãŒãã®å®è·è
ãšã³ãã£ãã£ã¯èªèº«ã®æŽåæ§ãä¿è·ããå¿ èŠããããŸããç¡å¹ãªç¶æ ã§`User`ãäœæããããšã¯äžå¯èœã§ããå¿ èŠããããŸãããã¡ã¯ããªãŒé¢æ°å ã«æ€èšŒã远å ããŸããããŒã¿ãç¡å¹ãªå Žåããã¡ã¯ããªãŒã¯ãšã©ãŒãã¹ããŒããäœãééã£ãŠããããæç¢ºã«èšè¿°ããå¿ èŠããããŸãã
// file: /domain/user.js
export default function buildMakeUser({ Id, isValidEmail, hashPassword }) {
return function makeUser({
id = Id.makeId(),
firstName,
lastName,
email,
password, // We now take a plain password and handle it inside
createdAt = new Date()
}) {
if (!Id.isValidId(id)) {
throw new Error('User must have a valid id.');
}
if (!firstName || firstName.length < 2) {
throw new Error('First name must be at least 2 characters long.');
}
if (!lastName || lastName.length < 2) {
throw new Error('Last name must be at least 2 characters long.');
}
if (!email || !isValidEmail(email)) {
throw new Error('User must have a valid email address.');
}
if (!password || password.length < 8) {
throw new Error('Password must be at least 8 characters long.');
}
// Data normalization and transformation happens here
const passwordHash = hashPassword(password);
const normalizedEmail = email.toLowerCase();
return Object.freeze({
getId: () => id,
getFirstName: () => firstName,
getLastName: () => lastName,
getEmail: () => normalizedEmail,
getPasswordHash: () => passwordHash,
getCreatedAt: () => createdAt
});
}
}
ããã§ã`User`ãäœæãããã·ã¹ãã ã®äžéšã¯ããã®ãã¡ã¯ããªãŒãééããå¿ èŠããããŸããæ¯åä¿èšŒãããæ€èšŒãåŸãããŸãããŸãããã¹ã¯ãŒãã®ããã·ã¥åãšã¡ãŒã«ã¢ãã¬ã¹ã®æ£èŠåã®ããžãã¯ãã«ãã»ã«åããŸãããã¢ããªã±ãŒã·ã§ã³ã®æ®ãã®éšåã¯ããããã®è©³çްãç¥ãå¿ èŠãæ°ã«ããå¿ èŠããããŸããã
3. ããžãã¹ããžãã¯ïŒåäœã®ã«ãã»ã«å
ç§ãã¡ã®`User`ãªããžã§ã¯ãã¯ããŸã å°ãè²§è¡ã§ããããŒã¿ãä¿æããŸãããäœã*ããŸãã*ãåäœãã€ãŸããã¡ã€ã³åºæã®ã¢ã¯ã·ã§ã³ã衚ãã¡ãœããã远å ããŸãããã
// ... inside the makeUser function ...
if (!password || password.length < 8) {
// ...
}
const passwordHash = hashPassword(password);
const normalizedEmail = email.toLowerCase();
return Object.freeze({
getId: () => id,
getFirstName: () => firstName,
getLastName: () => lastName,
getEmail: () => normalizedEmail,
getPasswordHash: () => passwordHash,
getCreatedAt: () => createdAt,
// Business Logic / Behavior
getFullName: () => `${firstName} ${lastName}`,
// A method that describes a business rule
canVote: () => {
// In some countries, voting age is 18. This is a business rule.
// Let's assume we have a dateOfBirth property.
const age = calculateAge(dateOfBirth);
return age >= 18;
}
});
// ...
`getFullName`ããžãã¯ã¯ããã¯ãã©ã³ãã ãªã³ã³ãããŒã©ãŒã«æ£åšããŠããŸãããããã¯`User`ãšã³ãã£ãã£èªäœã«å±ããŸãã`User`ãªããžã§ã¯ããæã€äººã¯èª°ã§ãã`user.getFullName()`ãåŒã³åºãããšã§ç¢ºå®ã«ãã«ããŒã ãååŸã§ããŸããããžãã¯ã¯1ã€ã®å Žæã«1åå®çŸ©ãããŸãã
å®è·µçãªäŸã®æ§ç¯ïŒã·ã³ãã«ãªeã³ããŒã¹ã·ã¹ãã
ãã®ãã¿ãŒã³ããããçžäºæ¥ç¶ããããã¡ã€ã³ã«é©çšããŠã¿ãŸãããã`Product`ã`OrderItem`ãããã³`Order`ãã¢ãã«åããŸãã
1. `Product`ãšã³ãã£ãã£ã®ã¢ããªã³ã°
補åã«ã¯ãååãäŸ¡æ Œãããã³åšåº«æ å ±ããããŸããååãå¿ èŠã§ãããäŸ¡æ Œã¯è² ã®æ°ã«ããããšã¯ã§ããŸããã
// file: /domain/product.js
export default function buildMakeProduct({ Id }) {
return function makeProduct({
id = Id.makeId(),
name,
description,
price,
stock = 0
}) {
if (!Id.isValidId(id)) {
throw new Error('Product must have a valid ID.');
}
if (!name || name.trim().length < 2) {
throw new Error('Product name must be at least 2 characters.');
}
if (isNaN(price) || price <= 0) {
throw new Error('Product must have a price greater than zero.');
}
if (isNaN(stock) || stock < 0) {
throw new Error('Stock must be a non-negative number.');
}
return Object.freeze({
getId: () => id,
getName: () => name,
getDescription: () => description,
getPrice: () => price,
getStock: () => stock,
// Business logic
isAvailable: () => stock > 0,
// A method that modifies state by returning a new instance
reduceStock: (amount) => {
if (amount > stock) {
throw new Error('Not enough stock available.');
}
// Return a NEW product object with the updated stock
return makeProduct({ id, name, description, price, stock: stock - amount });
}
});
}
}
`reduceStock`ã¡ãœããã«æ³šæããŠãã ãããããã¯ãäžå€æ§ã«é¢é£ããéèŠãªæŠå¿µã§ããæ¢åã®ãªããžã§ã¯ãã®`stock`ããããã£ã倿Žãã代ããã«ãæŽæ°ãããå€ãæã€*æ°ãã*`Product`ã€ã³ã¹ã¿ã³ã¹ãè¿ããŸããããã«ãããç¶æ ã®å€æŽãæç€ºçãã€äºæž¬å¯èœã«ãªããŸãã
2. `Order`ãšã³ãã£ãã£ã®ã¢ããªã³ã°ïŒã¢ã°ãªã²ãŒãã«ãŒãïŒ
`Order`ã¯ããè€éã§ããããã¯ããã¡ã€ã³é§åèšèšïŒDDDïŒããã¢ã°ãªã²ãŒãã«ãŒãããšåŒã¶ãã®ã§ããããã¯ãå¢çå ã®ä»ã®ããå°ããªãªããžã§ã¯ãã管çãããšã³ãã£ãã£ã§ãã`Order`ã«ã¯ã`OrderItem`ã®ãªã¹ããå«ãŸããŠããŸãã補åãçŽæ¥æ³šæã«è¿œå ããã®ã§ã¯ãªãã補åãšæ°éãå«ã`OrderItem`ã远å ããŸãã
// file: /domain/order.js
export const ORDER_STATUS = {
PENDING: 'PENDING',
PAID: 'PAID',
SHIPPED: 'SHIPPED',
CANCELLED: 'CANCELLED'
};
export default function buildMakeOrder({ Id, validateOrderItem }) {
return function makeOrder({
id = Id.makeId(),
customerId,
items = [],
status = ORDER_STATUS.PENDING,
createdAt = new Date()
}) {
if (!Id.isValidId(id)) {
throw new Error('Order must have a valid ID.');
}
if (!customerId) {
throw new Error('Order must have a customer ID.');
}
let orderItems = [...items]; // Create a private copy to manage
return Object.freeze({
getId: () => id,
getCustomerId: () => customerId,
getItems: () => [...orderItems], // Return a copy to prevent external modification
getStatus: () => status,
getCreatedAt: () => createdAt,
// Business Logic
calculateTotal: () => {
return orderItems.reduce((total, item) => {
return total + (item.getPrice() * item.getQuantity());
}, 0);
},
addItem: (item) => {
// validateOrderItem is a function that ensures the item is a valid OrderItem entity
validateOrderItem(item);
// Business rule: prevent adding duplicates, just increase quantity
const existingItemIndex = orderItems.findIndex(i => i.getProductId() === item.getProductId());
if (existingItemIndex > -1) {
const newQuantity = orderItems[existingItemIndex].getQuantity() + item.getQuantity();
// Here you'd update the quantity on the existing item
// (This requires items to be mutable or have an update method)
} else {
orderItems.push(item);
}
},
markPaid: () => {
if (status !== ORDER_STATUS.PENDING) {
throw new Error('Only pending orders can be marked as paid.');
}
// Return a new Order instance with the updated status
return makeOrder({ id, customerId, items: orderItems, status: ORDER_STATUS.PAID, createdAt });
}
});
}
}
ãã®`Order`ãšã³ãã£ãã£ã¯ãè€éãªããžãã¹ã«ãŒã«ãé©çšããŸãã
- ã¢ã€ãã ã®ç¬èªã®ãªã¹ãã管çããŸãã
- èªèº«ã®åèšãèšç®ããæ¹æ³ãç¥ã£ãŠããŸãã
- ç¶æ ã®é·ç§»ãé©çšããŸãïŒããšãã°ã`PENDING`ã®æ³šæã®ã¿ã`PAID`ãšããŠããŒã¯ã§ããŸãïŒã
泚æã®ããžãã¹ããžãã¯ã¯ããã®ã¢ãžã¥ãŒã«å ã«ãã¡ããšã«ãã»ã«åãããåé¢ããŠãã¹ãå¯èœã§ãããã¢ããªã±ãŒã·ã§ã³å šäœã§åå©çšã§ããŸãã
é«åºŠãªãã¿ãŒã³ãšèæ ®äºé
äžå€æ§ïŒäºæž¬å¯èœæ§ã®åºç€
äžå€æ§ã«ã€ããŠè§ŠããŸããããªãããããããªã«éèŠãªã®ã§ããïŒãªããžã§ã¯ããäžå€ã®å Žåãäºæããã«ç¶æ ãå€åããã®ã§ã¯ãªãããšå¿é ããããšãªããã¢ããªã±ãŒã·ã§ã³å šäœã§ããããæž¡ãããšãã§ããŸããããã«ããããã°ã®ã¯ã©ã¹å šäœãæé€ãããã¢ããªã±ãŒã·ã§ã³ã®ããŒã¿ãããŒãã¯ããã«ç°¡åã«æšè«ã§ããããã«ãªããŸãã
Object.freeze()ã¯ãæµ
ãããªãŒãºãæäŸããŸãããã¹ãããããªããžã§ã¯ããŸãã¯é
åïŒ`Order`ãªã©ïŒãæã€ãšã³ãã£ãã£ã®å Žåãããæ³šæããå¿
èŠããããŸããããšãã°ã`order.getItems()`ã§ã¯ãåŒã³åºãå
ãã¢ã€ãã ãæ³šæã®å
éšé
åã«çŽæ¥ããã·ã¥ã§ããªãããã«ãã³ããŒïŒ`[...orderItems]`ïŒãè¿ããŸããã
è€éãªã¢ããªã±ãŒã·ã§ã³ã®å ŽåãImmerã®ãããªã©ã€ãã©ãªã䜿çšãããšãäžå€ã®ãã¹ããããæ§é ã®æäœãã¯ããã«ç°¡åã«ãªããŸãããäžå¿ãšãªãååã¯å€ãããŸããããšã³ãã£ãã£ãäžå€ã®å€ãšããŠæ±ããŸãã倿Žãå¿ èŠãªå Žåã¯ãæ°ããå€ãäœæããŸãã
éåææäœãšæ°žç¶æ§ã®åŠç
ãšã³ãã£ãã£ãå®å šã«åæããŠããããšã«æ°ä»ãããããããŸãããããŒã¿ããŒã¹ãAPIã«ã€ããŠã¯äœãç¥ããŸãããããã¯æå³çã§ããããã¿ãŒã³ã®å€§ããªåŒ·ã¿ã§ãïŒ
ãšã³ãã£ãã£ã¯èªåèªèº«ãä¿åããã¹ãã§ã¯ãããŸããããšã³ãã£ãã£ã®ä»äºã¯ãããžãã¹ã«ãŒã«ãé©çšããããšã§ããããŒã¿ããŒã¹ã«ããŒã¿ãä¿åãããžã§ãã¯ãã¢ããªã±ãŒã·ã§ã³ã®å¥ã®ã¬ã€ã€ãŒã«å±ããŸããå€ãã®å ŽåããµãŒãã¹ã¬ã€ã€ãŒããŠãŒã¹ã±ãŒã¹ã¬ã€ã€ãŒããŸãã¯ãªããžããªãã¿ãŒã³ãšåŒã°ããŸãã
ããããã©ã®ããã«çžäºäœçšããããæ¬¡ã«ç€ºããŸãã
// file: /use-cases/create-user.js
// This use case depends on the user entity factory and a database access function.
export default function makeCreateUser({ makeUser, usersDatabase }) {
return async function createUser(userInfo) {
// 1. Create a valid domain entity. This step validates the data.
const user = makeUser(userInfo);
// 2. Check for business rules that require external data (e.g., email uniqueness)
const exists = await usersDatabase.findByEmail({ email: user.getEmail() });
if (exists) {
throw new Error('Email address is already in use.');
}
// 3. Persist the entity. The database needs a plain object.
const persisted = await usersDatabase.insert({
id: user.getId(),
firstName: user.getFirstName(),
// ... and so on
});
return persisted;
}
}
ãã®é¢å¿ã®åé¢ã¯åŒ·åã§ãã
- `User`ãšã³ãã£ãã£ã¯ãçŽç²ã§åæããŠãããåäœãã¹ããç°¡åã§ãã
- `createUser`ãŠãŒã¹ã±ãŒã¹ã¯ããªãŒã±ã¹ãã¬ãŒã·ã§ã³ãæ åœããã¢ãã¯ããŒã¿ããŒã¹ã䜿çšããŠçµ±åãã¹ãã§ããŸãã
- `usersDatabase`ã¢ãžã¥ãŒã«ã¯ãç¹å®ã®ããŒã¿ããŒã¹ãã¯ãããžãŒãæ åœããåå¥ã«ãã¹ãã§ããŸãã
ã·ãªã¢ã«åãšãã·ãªã¢ã«å
ãšã³ãã£ãã£ã¯ããã®ã¡ãœããã䜿çšãããšããªãããªããžã§ã¯ãã«ãªããŸãããã ãããããã¯ãŒã¯ïŒããšãã°ãJSON APIã¬ã¹ãã³ã¹ã§ïŒçµç±ã§ããŒã¿ãéä¿¡ããããããŒã¿ããŒã¹ã«ä¿åãããããå Žåã¯ããã¬ãŒã³ãªããŒã¿è¡šçŸãå¿ èŠã§ãããã®ããã»ã¹ã¯ãã·ãªã¢ã«åãšåŒã°ããŸãã
äžè¬çãªãã¿ãŒã³ã¯ããšã³ãã£ãã£ã«`toJSON()`ãŸãã¯`toObject()`ã¡ãœããã远å ããããšã§ãã
// ... inside the makeUser function ...
return Object.freeze({
getId: () => id,
// ... other getters
// Serialization method
toObject: () => ({
id,
firstName,
lastName,
email: normalizedEmail,
createdAt
// Notice we don't include the passwordHash
})
});
ããŒã¿ããŒã¹ãŸãã¯APIãããã¬ãŒã³ãªããŒã¿ãååŸããããããªãããªãã¡ã€ã³ãšã³ãã£ãã£ã«æ»ãéã®ããã»ã¹ã¯ã`makeUser`ãã¡ã¯ããªãŒé¢æ°ã®ç®çãã®ãã®ã§ããããã¯ããã·ãªã¢ã«åã§ãã
TypeScriptãŸãã¯JSDocã«ããåä»ã
ãã®ãã¿ãŒã³ã¯ããã©JavaScriptã§å®å šã«æ©èœããŸãããTypeScriptãŸãã¯JSDocã§éçåã远å ãããšãããã匷åãããŸããåã䜿çšãããšããšã³ãã£ãã£ã®ã圢ç¶ããæ£åŒã«å®çŸ©ããåªãããªãŒãã³ã³ããªãŒããšã³ã³ãã€ã«æãã§ãã¯ãæäŸã§ããŸãã
// file: /domain/user.ts
// Define the entity's public interface
export type User = Readonly<{
getId: () => string;
getFirstName: () => string;
// ... etc
getFullName: () => string;
}>;
// The factory function now returns the User type
export default function buildMakeUser(...) {
return function makeUser(...): User {
// ... implementation
}
}
ã¢ãžã¥ãŒã«ãšã³ãã£ãã£ãã¿ãŒã³ã®å æ¬çãªå©ç¹
ãã®ãã¿ãŒã³ãæ¡çšããããšã«ãããã¢ããªã±ãŒã·ã§ã³ã®æé·ã«ã€ããŠè€ååããã倿°ã®å©ç¹ãåŸãããŸãã
- å¯äžã®çå®ã®æºïŒããžãã¹ã«ãŒã«ãšããŒã¿æ€èšŒã¯ãäžå åãããæç¢ºã«ãªã£ãŠããŸããã«ãŒã«ãžã®å€æŽã¯ãæ£ç¢ºã«1ãæã§è¡ãããŸãã
- é«ãåé床ãäœãçµå床ïŒãšã³ãã£ãã£ã¯èªå·±å®çµåã§ãããå€éšã·ã¹ãã ã«äŸåããŸãããããã«ãããã³ãŒãããŒã¹ãã¢ãžã¥ãŒã«åããããªãã¡ã¯ã¿ãªã³ã°ã容æã«ãªããŸãã
- æé«ã®ãã¹ãå®¹ææ§ïŒäžçå šäœãã¢ãã¯ããããšãªããæãéèŠãªããžãã¹ããžãã¯ã«å¯ŸããŠãã·ã³ãã«ã§é«éãªåäœãã¹ããäœæã§ããŸãã
- éçºè ãšã¯ã¹ããªãšã³ã¹ã®åäžïŒéçºè ã`User`ãæäœããå¿ èŠãããå Žåãæç¢ºã§äºæž¬å¯èœã§èªå·±ææžåãããAPIã䜿çšã§ããŸãããã¬ãŒã³ãªããžã§ã¯ãã®åœ¢ç¶ãæšæž¬ããå¿ èŠã¯ãããããŸããã
- ã¹ã±ãŒã©ããªãã£ã®åºç€ïŒãã®ãã¿ãŒã³ã¯ãå®å®ããä¿¡é Œæ§ã®é«ãã³ã¢ãæäŸããŸããããå€ãã®æ©èœããã¬ãŒã ã¯ãŒã¯ããŸãã¯UIã³ã³ããŒãã³ãã远å ãããšãããžãã¹ããžãã¯ã¯ä¿è·ãããäžè²«æ§ãä¿ãããŸãã
çµè«ïŒã¢ããªã±ãŒã·ã§ã³ã®å ç¢ãªã³ã¢ãæ§ç¯ãã
åãã®éããã¬ãŒã ã¯ãŒã¯ãšã©ã€ãã©ãªã®äžçã§ã¯ããããã®ããŒã«ã¯äžæçãªãã®ã§ããããšãå¿ããã¡ã§ãããããã¯å€åããŸããæ°žç¶ããã®ã¯ãããžãã¹ãã¡ã€ã³ã®äžæ žãšãªãããžãã¯ã§ãããã®ãã¡ã€ã³ã®é©åãªã¢ããªã³ã°ã«æéãè²»ããããšã¯ãåãªãåŠè¡çãªæŒç¿ã§ã¯ãããŸãããããã¯ããœãããŠã§ã¢ã®å¥å šæ§ãšå¯¿åœã«å¯ŸããŠè¡ãããšãã§ããæãéèŠãªé·ææè³ã®1ã€ã§ãã
JavaScriptã¢ãžã¥ãŒã«ãšã³ãã£ãã£ãã¿ãŒã³ã¯ããããã®ã¢ã€ãã¢ãå®è£ ããããã®ã·ã³ãã«ã§åŒ·åã§ãã€ãã£ããªæ¹æ³ãæäŸããŸããéããã¬ãŒã ã¯ãŒã¯ãè€éãªã»ããã¢ããã¯å¿ èŠãããŸãããèšèªã®åºæ¬çãªæ©èœïŒã¢ãžã¥ãŒã«ã颿°ãã¯ããŒãžã£ãŒïŒã掻çšããŠãã¢ããªã±ãŒã·ã§ã³ã®ã¯ãªãŒã³ã§å埩åããããçè§£ããããã³ã¢ãæ§ç¯ããã®ã«åœ¹ç«ã¡ãŸããæ¬¡ã®ãããžã§ã¯ãã§1ã€ã®ããŒãšã³ãã£ãã£ããå§ããŸãããã®ããããã£ãã¢ãã«åãããã®äœæãæ€èšŒããããã«åäœãäžããŸããããå ç¢ã§ãããã§ãã·ã§ãã«ãªãœãããŠã§ã¢ã¢ãŒããã¯ãã£ã«åããŠæåã®ã¹ããããèžã¿åºãããšã«ãªããŸãã