Hej
Mam za zadanie napisać repozytorium z lokalnym storagem, wymaganiem jest, żeby było wszytko thread safe. Wybrałem do tego ConcurrencyHashMap, czy takie rozwiązanie jest ok ?
public class InMemoryRepository implements Repository {
private ConcurrentHashMap<Integer, Note> noteStorage;
public InMemoryRepository() {
this.noteStorage = new ConcurrentHashMap<>();
}
@Override
public void save(Note note) {
noteStorage.put(note.getId(), note);
}
@Override
public Optional<Note> findById(int id) {
return Optional.ofNullable(noteStorage.get(id));
}
@Override
public Optional<Note> update(Note note){
return Optional.ofNullable(noteStorage.put(note.getId(), note));
}
@Override
public Collection<Note> findAll() {
return noteStorage
.entrySet()
.stream()
.map(Map.Entry::getValue)
.collect(Collectors.toList());
}
@Override
public Collection<Note> findCompletedNorteByCategoryAndSubCategoryAndMarketRefName(String category, String subCategory, String refName) {
return noteStorage
.entrySet()
.parallelStream()
.map(Map.Entry::getValue)
.filter(Note::isCompleted)
.filter(note -> (refName == null || note.hasMarketName(refName)))
.filter(note -> {
if (category != null && subCategory != null) {
final SubCategory noteSubCategory = note.getSubCategory();
return noteSubCategory.getName().equals(subCategory) && noteSubCategory.getCategory().getName().equals(category);
} else {
return true;
}
})
.collect(Collectors.toList());
}
}
Mam takze podany interfejs NoteClient który musze zaimplementowac, oczywiscie wszystko thread safe, nalezy przypomniec ze Note ma wszystkie pola final wiec jak zmieniam jakas zmienna to zwracam nowy obiekt (Note jest Immutable):
public class NoteClientImpl implements NoteClient{
private final NoteRepository noteRepository;
private final Object updateLock = new Object();
public ClientImpl(NoteRepository noteRepository){
Objects.requireNonNull(noteRepository, "NoteRepository can not be null");
this.noteRepository = noteRepository;
}
@Override
public void addNote(Note note) {
noteRepository.save(note);
}
@Override
public void noteCompleted(Integer id) {
noteRepository
.findById(id)
.map(note -> {
synchronized (updateLock) {
Note updatedNote = note.updateCompleted();
return noteRepository.update(updatedNote);
}
});
}
@Override
public void addRefTypeToNote(Integer id, RefType type) {
noteRepository
.findById(id)
.map(note->{
synchronized (updateLock) {
Note updatedNote = note.addRefType(type);
return noteRepository.update(updatedNote);
}
});
}
@Override
public void removeRefTypeFromNote(Integer id, RefType type) {
noteRepository
.findById(id)
.map(note->{
synchronized (updateLock) {
Note updatedNote = note.removeRefType(type);
return noteRepository.update(updatedNote);
}
});
}
@Override
public Collection<String> futureNote(String category, String subCategory, String nameRef) {
return noteRepository
.findCompletedNorteByCategoryAndSubCategoryAndMarketRefName(category, subCategory, nameRef)
.stream()
.map(Note::getName)
.collect(Collectors.toList());
}
@Override
public String allStructure() {
return noteRepository
.findAll()
.stream()
.map(Note::toString)
.collect(Collectors.joining(", "));
}
}
Obiekt Note, tych pól i implementacji nie można zmieniać, takie wymagania:
public class Note implements Serializable {
private final Integer id;
private final String name;
private final SubCategory subCategory;
private final Collection<RefType> refTypes;
private final Boolean completed;
public Note(Integer id,
String name,
SubCategory subCategory,
Collection<RefType> refTypes,
Boolean completed
) {
Objects.requireNonNull(id, "Id can not be null");
Objects.requireNonNull(name, "Name can not be null");
Objects.requireNonNull(subCategory, "SubCategory can not be null");
Objects.requireNonNull(completed, "Completed can not be null");
this.id = id;
this.name = name;
this.subCategory = subCategory;
//I do not know business domain co i think that event can exist without marketRefTypes
this.refTypes = (refTypes != null ? refTypes : Collections.emptyList());
this.completed = completed;
}
......
//metody dodane przeze mnie:
public Note addRefType(RefType refType) {
ensureNotCompleted();
if (hasRefName(refType.getRefName())) {
return this;
}
Collection<RefType> updatedType = getRefTypes();
updatedType.add(refType);
return new Note(id, name, subCategory, updatedType, completed);
}
public Note removeRefType(RefType refType) {
ensureNotCompleted();
if (hasRefName(refType.getRefName())) {
Collection<RefType> updatedRefTypes = getRefTypes();
updatedRefTypes.remove(refType);
return new Note(id, name, subCategory, updatedRefTypes, completed);
}
return this;
}
private void ensureNotCompleted() {
if (getCompleted()) {
throw new NoteAlreadyCompletedException();
}
}
public Note updateCompleted() {
ensureNotCompleted();
return new Note(id, name, subCategory, marketRefTypes, true);
}
}
Zastanawiam się nad 3 sprawami :
- Musze synchronizowac wszystkie operacje update na evencie i najlepiej chyba do tego dodac obiekt zamiast this aby nie blokowac odczytów ?
- Jak można przeprowadzić testy czy wszystko jest thread safe ? Czy wszystkie operacje są ok?
- Czy można to zaimplementować lepiej ?