22import org.springframework.stereotype.Service;
23import org.springframework.transaction.annotation.Transactional;
24
25@Service 26@Transactional(readOnly = true) 27@RequiredArgsConstructor 28public class UserService implements UserDetailsService { 29 30 private final UserRepo userRepo; 31 private final UserEditMapper userEditMapper; 32 private final UserViewMapper userViewMapper; 33 private final PasswordEncoder passwordEncoder; 34 35 @Transactional 36 public UserView create(CreateUserRequest request) { 37 if (userRepo.findByUsername(request.username()).isPresent()) { 38 throw new ValidationException("Username exists!"); 39 } 40 if (!request.password().equals(request.rePassword())) { 41 throw new ValidationException("Passwords don't match!"); 42 } 43 44 var user = userEditMapper.create(request); 45 user.setPassword(passwordEncoder.encode(request.password())); 46 47 user = userRepo.save(user); 48 49 return userViewMapper.toUserView(user); 50 } 51 52 @Transactional 53 public UserView update(ObjectId id, UpdateUserRequest request) { 54 var user = userRepo.getById(id); 55 userEditMapper.update(request, user); 56 57 user = userRepo.save(user); 58 59 return userViewMapper.toUserView(user); 60 } 61 62 @Transactional 63 public UserView upsert(CreateUserRequest request) { 64 var optionalUser = userRepo.findByUsername(request.username()); 65 66 if (optionalUser.isEmpty()) { 67 return create(request); 68 } else { 69 UpdateUserRequest updateUserRequest = 70 new UpdateUserRequest(request.fullName(), request.authorities()); 71 return update(optionalUser.get().getId(), updateUserRequest); 72 } 73 } 74 75 @Transactional 76 public UserView delete(ObjectId id) { 77 var user = userRepo.getById(id); 78 79 user.setUsername( 80 user.getUsername().replace("@", String.format("_%s@", user.getId().toString()))); 81 user.setEnabled(false); 82 user = userRepo.save(user); 83 84 return userViewMapper.toUserView(user); 85 } 86 87 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 88 return userRepo 89 .findByUsername(username) 90 .orElseThrow( 91 () -> 92 new UsernameNotFoundException( 93 format("User with username - %s, not found", username))); 94 } 95 96 public boolean usernameExists(String username) { 97 return userRepo.findByUsername(username).isPresent(); 98 } 99100 public UserView getUser(ObjectId id) {101 return userViewMapper.toUserView(userRepo.getById(id));102 }103104 public List<UserView> searchUsers(Page page, SearchUsersQuery query) {105 List<User> users = userRepo.searchUsers(page, query);106 return userViewMapper.toUserView(users);107 }108}
16import org.springframework.transaction.annotation.Transactional;
17import org.springframework.util.CollectionUtils;
18
19@Service20@Transactional(readOnly = true)21@RequiredArgsConstructor22public class BookService {2324 private final BookRepo bookRepo;25 private final AuthorRepo authorRepo;26 private final BookEditMapper bookEditMapper;27 private final BookViewMapper bookViewMapper;2829 @Transactional30 public BookView create(EditBookRequest request) {31 var book = bookEditMapper.create(request);3233 book = bookRepo.save(book);34 updateAuthors(book);3536 return bookViewMapper.toBookView(book);37 }3839 @Transactional40 public BookView update(ObjectId id, EditBookRequest request) {41 var book = bookRepo.getById(id);42 bookEditMapper.update(request, book);4344 book = bookRepo.save(book);45 if (!CollectionUtils.isEmpty(request.authorIds())) {46 updateAuthors(book);47 }4849 return bookViewMapper.toBookView(book);50 }5152 private void updateAuthors(Book book) {53 var authors = authorRepo.findAllById(book.getAuthorIds());54 authors.forEach(author -> author.getBookIds().add(book.getId()));55 authorRepo.saveAll(authors);56 }5758 @Transactional59 public BookView delete(ObjectId id) {60 var book = bookRepo.getById(id);6162 bookRepo.delete(book);6364 return bookViewMapper.toBookView(book);65 }6667 public BookView getBook(ObjectId id) {68 var book = bookRepo.getById(id);69 return bookViewMapper.toBookView(book);70 }7172 public List<BookView> getBooks(Iterable<ObjectId> ids) {73 var books = bookRepo.findAllById(ids);74 return bookViewMapper.toBookView(books);75 }7677 public List<BookView> getAuthorBooks(ObjectId authorId) {78 var author = authorRepo.getById(authorId);79 return bookViewMapper.toBookView(bookRepo.findAllById(author.getBookIds()));80 }8182 public List<BookView> searchBooks(Page page, SearchBooksQuery query) {83 return bookViewMapper.toBookView(bookRepo.searchBooks(page, query));84 }85}
14import org.springframework.stereotype.Service;
15import org.springframework.transaction.annotation.Transactional;
16
17@Service18@Transactional(readOnly = true)19@RequiredArgsConstructor20public class AuthorService {2122 private final AuthorRepo authorRepo;23 private final BookRepo bookRepo;24 private final AuthorEditMapper authorEditMapper;25 private final AuthorViewMapper authorViewMapper;2627 @Transactional28 public AuthorView create(EditAuthorRequest request) {29 var author = authorEditMapper.create(request);3031 author = authorRepo.save(author);3233 return authorViewMapper.toAuthorView(author);34 }3536 @Transactional37 public AuthorView update(ObjectId id, EditAuthorRequest request) {38 var author = authorRepo.getById(id);39 authorEditMapper.update(request, author);4041 author = authorRepo.save(author);4243 return authorViewMapper.toAuthorView(author);44 }4546 @Transactional47 public AuthorView delete(ObjectId id) {48 var author = authorRepo.getById(id);4950 authorRepo.delete(author);51 bookRepo.deleteAll(bookRepo.findAllById(author.getBookIds()));5253 return authorViewMapper.toAuthorView(author);54 }5556 public AuthorView getAuthor(ObjectId id) {57 return authorViewMapper.toAuthorView(authorRepo.getById(id));58 }5960 public List<AuthorView> getAuthors(Iterable<ObjectId> ids) {61 return authorViewMapper.toAuthorView(authorRepo.findAllById(ids));62 }6364 public List<AuthorView> getBookAuthors(ObjectId bookId) {65 var book = bookRepo.getById(bookId);66 return authorViewMapper.toAuthorView(authorRepo.findAllById(book.getAuthorIds()));67 }6869 public List<AuthorView> searchAuthors(Page page, SearchAuthorsQuery query) {70 return authorViewMapper.toAuthorView(authorRepo.searchAuthors(page, query));71 }72}
60 Optional<User> findByUsername(String username);
61}
62
63interface UserRepoCustom { 64 65 List<User> searchUsers(Page page, SearchUsersQuery query); 66} 67
68@RequiredArgsConstructor
69class UserRepoCustomImpl implements UserRepoCustom {
65 List<User> searchUsers(Page page, SearchUsersQuery query);
66}
67
68@RequiredArgsConstructor 69class UserRepoCustomImpl implements UserRepoCustom { 70 71 private final MongoTemplate mongoTemplate; 72 73 @Override 74 public List<User> searchUsers(Page page, SearchUsersQuery query) { 75 var operations = new ArrayList<AggregationOperation>(); 76 77 var criteriaList = new ArrayList<Criteria>(); 78 if (StringUtils.hasText(query.id())) { 79 criteriaList.add(Criteria.where("id").is(new ObjectId(query.id()))); 80 } 81 if (StringUtils.hasText(query.username())) { 82 criteriaList.add(Criteria.where("username").regex(query.username(), "i")); 83 } 84 if (StringUtils.hasText(query.fullName())) { 85 criteriaList.add(Criteria.where("fullName").regex(query.fullName(), "i")); 86 } 87 if (!criteriaList.isEmpty()) { 88 Criteria userCriteria = new Criteria().andOperator(criteriaList.toArray(new Criteria[0])); 89 operations.add(match(userCriteria)); 90 } 91 92 operations.add(sort(Sort.Direction.DESC, "createdAt")); 93 operations.add(skip((page.number() - 1) * page.limit())); 94 operations.add(limit(page.limit())); 95 96 var aggregation = newAggregation(User.class, operations); 97 var results = mongoTemplate.aggregate(aggregation, User.class); 98 return results.getMappedResults(); 99 }100}
27import org.springframework.stereotype.Repository;
28import org.springframework.util.StringUtils;
29
30@Repository 31@CacheConfig(cacheNames = "users") 32public interface UserRepo extends UserRepoCustom, MongoRepository<User, ObjectId> { 33 34 @CacheEvict(allEntries = true) 35 <S extends User> List<S> saveAll(Iterable<S> entities); 36 37 @Caching( 38 evict = { 39 @CacheEvict(key = "#p0.id", condition = "#p0.id != null"), 40 @CacheEvict(key = "#p0.username", condition = "#p0.username != null") 41 }) 42 <S extends User> S save(S entity); 43 44 @Cacheable 45 Optional<User> findById(ObjectId objectId); 46 47 @Cacheable 48 default User getById(ObjectId id) { 49 var optionalUser = findById(id); 50 if (optionalUser.isEmpty()) { 51 throw new NotFoundException(User.class, id); 52 } 53 if (!optionalUser.get().isEnabled()) { 54 throw new NotFoundException(User.class, id); 55 } 56 return optionalUser.get(); 57 } 58 59 @Cacheable 60 Optional<User> findByUsername(String username); 61} 62
63interface UserRepoCustom {
64
35 List<Book> findAllById(Iterable<ObjectId> ids);
36}
37
38interface BookRepoCustom { 39 40 List<Book> searchBooks(Page page, SearchBooksQuery query); 41} 42
43@RequiredArgsConstructor
44class BookRepoCustomImpl implements BookRepoCustom {
40 List<Book> searchBooks(Page page, SearchBooksQuery query);
41}
42
43@RequiredArgsConstructor 44class BookRepoCustomImpl implements BookRepoCustom { 45 46 private final MongoTemplate mongoTemplate; 47 48 @Override 49 public List<Book> searchBooks(Page page, SearchBooksQuery query) { 50 var operations = new ArrayList<AggregationOperation>(); 51 52 var criteriaList = new ArrayList<Criteria>(); 53 if (StringUtils.hasText(query.id())) { 54 criteriaList.add(Criteria.where("id").is(new ObjectId(query.id()))); 55 } 56 if (StringUtils.hasText(query.creatorId())) { 57 criteriaList.add(Criteria.where("creatorId").is(new ObjectId(query.creatorId()))); 58 } 59 if (query.createdAtStart() != null) { 60 criteriaList.add(Criteria.where("createdAt").gte(query.createdAtStart())); 61 } 62 if (query.createdAtEnd() != null) { 63 criteriaList.add(Criteria.where("createdAt").lt(query.createdAtEnd())); 64 } 65 if (StringUtils.hasText(query.title())) { 66 criteriaList.add(Criteria.where("title").regex(query.title(), "i")); 67 } 68 if (!CollectionUtils.isEmpty(query.genres())) { 69 criteriaList.add(Criteria.where("genres").all(query.genres())); 70 } 71 if (StringUtils.hasText(query.isbn13())) { 72 criteriaList.add(Criteria.where("isbn13").is(query.isbn13())); 73 } 74 if (StringUtils.hasText(query.isbn10())) { 75 criteriaList.add(Criteria.where("isbn10").is(query.isbn10())); 76 } 77 if (StringUtils.hasText(query.publisher())) { 78 criteriaList.add(Criteria.where("publisher").regex(query.publisher(), "i")); 79 } 80 if (query.publishDateStart() != null) { 81 criteriaList.add(Criteria.where("publishDate").gte(query.publishDateStart())); 82 } 83 if (query.publishDateEnd() != null) { 84 criteriaList.add(Criteria.where("publishDate").lt(query.publishDateEnd())); 85 } 86 if (!criteriaList.isEmpty()) { 87 var bookCriteria = new Criteria().andOperator(criteriaList.toArray(new Criteria[0])); 88 operations.add(match(bookCriteria)); 89 } 90 91 criteriaList = new ArrayList<Criteria>(); 92 if (StringUtils.hasText(query.authorId())) { 93 criteriaList.add(Criteria.where("author._id").is(new ObjectId(query.authorId()))); 94 } 95 if (StringUtils.hasText(query.authorFullName())) { 96 criteriaList.add(Criteria.where("author.fullName").regex(query.authorFullName(), "i")); 97 } 98 if (!criteriaList.isEmpty()) { 99 var authorCriteria = new Criteria().andOperator(criteriaList.toArray(new Criteria[0]));100 operations.add(lookup("authors", "authorIds", "_id", "author"));101 operations.add(unwind("author", false));102 operations.add(match(authorCriteria));103 }104105 operations.add(sort(Sort.Direction.DESC, "createdAt"));106 operations.add(skip((page.number() - 1) * page.limit()));107 operations.add(limit(page.limit()));108109 var aggregation = newAggregation(Book.class, operations);110 var results = mongoTemplate.aggregate(aggregation, Book.class);111 return results.getMappedResults();112 }113}
25import org.springframework.util.CollectionUtils;
26import org.springframework.util.StringUtils;
27
28@Repository 29public interface BookRepo extends MongoRepository<Book, ObjectId>, BookRepoCustom { 30 31 default Book getById(ObjectId id) { 32 return findById(id).orElseThrow(() -> new NotFoundException(Book.class, id)); 33 } 34 35 List<Book> findAllById(Iterable<ObjectId> ids); 36} 37
38interface BookRepoCustom {
39
35 List<Author> findAllById(Iterable<ObjectId> ids);
36}
37
38interface AuthorRepoCustom {3940 List<Author> searchAuthors(Page page, SearchAuthorsQuery query);41}42
43@RequiredArgsConstructor
44class AuthorRepoCustomImpl implements AuthorRepoCustom {
25import org.springframework.util.CollectionUtils;
26import org.springframework.util.StringUtils;
27
28@Repository29public interface AuthorRepo extends MongoRepository<Author, ObjectId>, AuthorRepoCustom {3031 default Author getById(ObjectId id) {32 return findById(id).orElseThrow(() -> new NotFoundException(Author.class, id));33 }3435 List<Author> findAllById(Iterable<ObjectId> ids);36}37
38interface AuthorRepoCustom {
39
40 List<Author> searchAuthors(Page page, SearchAuthorsQuery query);
41}
42
43@RequiredArgsConstructor44class AuthorRepoCustomImpl implements AuthorRepoCustom {4546 private final MongoTemplate mongoTemplate;4748 @Override49 public List<Author> searchAuthors(Page page, SearchAuthorsQuery query) {50 var operations = new ArrayList<AggregationOperation>();5152 var criteriaList = new ArrayList<Criteria>();53 if (StringUtils.hasText(query.id())) {54 criteriaList.add(Criteria.where("id").is(new ObjectId(query.id())));55 }56 if (StringUtils.hasText(query.creatorId())) {57 criteriaList.add(Criteria.where("creatorId").is(new ObjectId(query.creatorId())));58 }59 if (query.createdAtStart() != null) {60 criteriaList.add(Criteria.where("createdAt").gte(query.createdAtStart()));61 }62 if (query.createdAtEnd() != null) {63 criteriaList.add(Criteria.where("createdAt").lt(query.createdAtEnd()));64 }65 if (StringUtils.hasText(query.fullName())) {66 criteriaList.add(Criteria.where("fullName").regex(query.fullName(), "i"));67 }68 if (!CollectionUtils.isEmpty(query.genres())) {69 criteriaList.add(Criteria.where("genres").all(query.genres()));70 }71 if (!criteriaList.isEmpty()) {72 var authorCriteria = new Criteria().andOperator(criteriaList.toArray(new Criteria[0]));73 operations.add(match(authorCriteria));74 }7576 criteriaList = new ArrayList<Criteria>();77 if (StringUtils.hasText(query.bookId())) {78 criteriaList.add(Criteria.where("book._id").is(new ObjectId(query.bookId())));79 }80 if (StringUtils.hasText(query.bookTitle())) {81 criteriaList.add(Criteria.where("book.title").regex(query.bookTitle(), "i"));82 }83 if (!criteriaList.isEmpty()) {84 var bookCriteria = new Criteria().andOperator(criteriaList.toArray(new Criteria[0]));85 operations.add(lookup("books", "bookIds", "_id", "book"));86 operations.add(unwind("book", false));87 operations.add(match(bookCriteria));88 }8990 operations.add(sort(Sort.Direction.DESC, "createdAt"));91 operations.add(skip((page.number() - 1) * page.limit()));92 operations.add(limit(page.limit()));9394 var aggregation = newAggregation(Author.class, operations);95 var results = mongoTemplate.aggregate(aggregation, Author.class);96 return results.getMappedResults();97 }98}
14import java.util.HashSet;
15import java.util.Set;
16
17@Document(collection = "users")18@Getter @Setter19public class User extends ComparableEntity implements UserDetails {2021 @Id22 private ObjectId id;2324 @CreatedDate25 private LocalDateTime createdAt;26 @LastModifiedDate27 private LocalDateTime modifiedAt;2829 private boolean enabled = true;3031 @Indexed(unique = true)32 private String username;33 private String password;34 @Indexed35 private String fullName;36 private Set<Role> authorities = new HashSet<>();3738 public User() {39 }4041 public User(String username, String password) {42 this.username = username;43 this.password = password;44 }4546 @Override47 public boolean isAccountNonExpired() {48 return enabled;49 }5051 @Override52 public boolean isAccountNonLocked() {53 return enabled;54 }5556 @Override57 public boolean isCredentialsNonExpired() {58 return enabled;59 }60}
5import lombok.NoArgsConstructor;
6import org.springframework.security.core.GrantedAuthority;
7
8@Data 9@AllArgsConstructor10@NoArgsConstructor11public class Role implements GrantedAuthority {1213 public static final String USER_ADMIN = "USER_ADMIN";14 public static final String AUTHOR_ADMIN = "AUTHOR_ADMIN";15 public static final String BOOK_ADMIN = "BOOK_ADMIN";1617 private String authority;1819}
4
5import java.io.Serializable;
6
7public abstract class ComparableEntity implements Serializable { 8 9 public abstract ObjectId getId();1011 @Override12 public boolean equals(Object o) {13 if (this == o) {14 return true;15 }16 if (o == null || this.getClass() != o.getClass()) {17 return false;18 }19 ComparableEntity that = (ComparableEntity) o;20 return getId().equals(that.getId());21 }2223 @Override24 public int hashCode() {25 return getId().hashCode();26 }27}
15import java.util.HashSet;
16import java.util.Set;
17
18@Document(collection = "books")19@Getter @Setter20public class Book extends ComparableEntity {2122 @Id23 private ObjectId id;2425 @CreatedBy26 private ObjectId creatorId;27 @LastModifiedBy28 private ObjectId modifierId;2930 @CreatedDate31 private LocalDateTime createdAt;32 @LastModifiedDate33 private LocalDateTime modifiedAt;3435 private String title;36 private String about;37 private String language;38 private Set<String> genres = new HashSet<>();39 private String isbn13;40 private String isbn10;41 private String publisher;42 private LocalDate publishDate;43 private int hardcover;4445 private Set<ObjectId> authorIds = new HashSet<>();4647}
14import java.util.HashSet;
15import java.util.Set;
16
17@Document(collection = "authors")18@Getter @Setter19public class Author extends ComparableEntity {2021 @Id22 private ObjectId id;2324 @CreatedBy25 private ObjectId creatorId;26 @LastModifiedBy27 private ObjectId modifierId;2829 @CreatedDate30 private LocalDateTime createdAt;31 @LastModifiedDate32 private LocalDateTime modifiedAt;3334 private String fullName;35 private String about;36 private String nationality;37 private Set<String> genres = new HashSet<>();3839 private Set<ObjectId> bookIds = new HashSet<>();4041}
9
10import java.util.List;
11
12@Mapper(componentModel = "spring", uses = {ObjectIdMapper.class})13public abstract class UserViewMapper {1415 @Autowired16 private UserRepo userRepo;1718 public abstract UserView toUserView(User user);1920 public abstract List<UserView> toUserView(List<User> users);2122 public UserView toUserViewById(ObjectId id) {23 if (id == null) {24 return null;25 }26 return toUserView(userRepo.getById(id));27 }2829}
17import static org.mapstruct.NullValueCheckStrategy.ALWAYS;
18import static org.mapstruct.NullValuePropertyMappingStrategy.IGNORE;
19
20@Mapper(componentModel = "spring", uses = ObjectIdMapper.class)21public abstract class UserEditMapper {2223 @Mapping(source = "authorities", target = "authorities", qualifiedByName = "stringToRole")24 public abstract User create(CreateUserRequest request);2526 @BeanMapping(nullValueCheckStrategy = ALWAYS, nullValuePropertyMappingStrategy = IGNORE)27 @Mapping(source = "authorities", target = "authorities", qualifiedByName = "stringToRole")28 public abstract void update(UpdateUserRequest request, @MappingTarget User user);2930 @Named("stringToRole")31 protected Set<Role> stringToRole(Set<String> authorities) {32 if (authorities != null) {33 return authorities.stream().map(Role::new).collect(toSet());34 }35 return new HashSet<>();36 }3738}
3import org.bson.types.ObjectId;
4import org.mapstruct.Mapper;
5
6@Mapper(componentModel = "spring") 7public class ObjectIdMapper { 8 9 public String objectIdToString(ObjectId objectId) {10 return objectId.toString();11 }1213 public ObjectId stringToObjectId(String string) {14 return new ObjectId(string);15 }1617}
11
12import java.util.List;
13
14@Mapper(componentModel = "spring", uses = ObjectIdMapper.class)15public abstract class BookViewMapper {1617 private UserViewMapper userViewMapper;1819 @Autowired20 public void setUserViewMapper(UserViewMapper userViewMapper) {21 this.userViewMapper = userViewMapper;22 }2324 @Mapping(source = "creatorId", target = "creator", qualifiedByName = "idToUserView")25 public abstract BookView toBookView(Book book);2627 public abstract List<BookView> toBookView(List<Book> books);2829 @Named("idToUserView")30 protected UserView idToUserView(ObjectId id) {31 return userViewMapper.toUserViewById(id);32 }33}
9import static org.mapstruct.NullValueCheckStrategy.ALWAYS;
10import static org.mapstruct.NullValuePropertyMappingStrategy.IGNORE;
11
12@Mapper(componentModel = "spring", uses = ObjectIdMapper.class)13public interface BookEditMapper {1415 Book create(EditBookRequest request);1617 @BeanMapping(nullValueCheckStrategy = ALWAYS, nullValuePropertyMappingStrategy = IGNORE)18 void update(EditBookRequest request, @MappingTarget Book book);1920}
11
12import java.util.List;
13
14@Mapper(componentModel = "spring", uses = ObjectIdMapper.class)15public abstract class AuthorViewMapper {1617 private UserViewMapper userViewMapper;1819 @Autowired20 public void setUserViewMapper(UserViewMapper userViewMapper) {21 this.userViewMapper = userViewMapper;22 }2324 @Mapping(source = "creatorId", target = "creator", qualifiedByName = "idToUserView")25 public abstract AuthorView toAuthorView(Author author);2627 public abstract List<AuthorView> toAuthorView(List<Author> authors);2829 @Named("idToUserView")30 protected UserView idToUserView(ObjectId id) {31 return userViewMapper.toUserViewById(id);32 }33}
9import static org.mapstruct.NullValueCheckStrategy.ALWAYS;
10import static org.mapstruct.NullValuePropertyMappingStrategy.IGNORE;
11
12@Mapper(componentModel = "spring", uses = ObjectIdMapper.class)13public interface AuthorEditMapper {1415 Author create(EditAuthorRequest request);1617 @BeanMapping(nullValueCheckStrategy = ALWAYS, nullValuePropertyMappingStrategy = IGNORE)18 void update(EditAuthorRequest request, @MappingTarget Author author);1920}
2
3import org.bson.types.ObjectId;
4
5public class NotFoundException extends RuntimeException { 6 7 public NotFoundException(String message) { 8 super(message); 9 }1011 public NotFoundException(Class<?> clazz, long id) {12 super(String.format("Entity %s with id %d not found", clazz.getSimpleName(), id));13 }1415 public NotFoundException(Class<?> clazz, String id) {16 super(String.format("Entity %s with id %s not found", clazz.getSimpleName(), id));17 }1819 public NotFoundException(Class<?> clazz, ObjectId id) {20 super(String.format("Entity %s with id %s not found", clazz.getSimpleName(), id.toString()));21 }2223}
This class does not have any documentation.
Consider adding a documentation comment to explain its use.
While it may seem like the functionality of a class is perfectly obvious, any consumers of your API may not be able to pick up on certain details.
Bad Practice
Consider a case where the class given below can be instantiated and provides certain functionalities within each instance in a thread-safe manner, perhaps it is a rest API client.
If there is no documentation comment on the class, it is not immediately obvious that the class is thread safe. Thus, multiple instances of the class may be created to perform operations concurrently, using up both memory as well as OS resources like sockets. If it were known from the beginning that the class were thread safe, the user would not need to create unnecessary extra instances of SomeClass
.
class SomeClass {
// ...
}
Recommended
Make sure to add useful information regarding the usage or implementation of a particular declaration, so that anything about it which can't be understood from the name or some other cue is correctly conveyed.
/**
* Instances of this class are used to perform xyz action.
*
* This class is thread safe and the same instance can be used over multiple threads.
*/
class SomeClass {
// ...
}
Exceptions
This issue will not be reported for model entity classes. If there is any non-obvious behavior associated with a particular class however, do consider documenting it.