Zapisywanie wiadomości z czatu

0

Piszę coś w rodzaju Messengera i zastanawia mnie jak przechowywać wiadomości.
Obecnie wygląda to tak:

  • FE wysyła wiadomość przez WebSocket do BE
  • BE dokonuje autentykacji i zapisuje wiadomość w bazie danych (MongoDB) - docelowo zapis chcę zrobić asynchroniczny
  • BE wysyła wiadomość do wskazanego w niej odbiorcy

W bazie mam wtedy coś takiego

@Document(collection = CollectionNames.MESSAGES)
public class MessageEntity {
    @Id
    private final String id;
    private final String sender;
    private final String receiver;
    private final String payload;
    private final LocalDateTime sentAt;
}

No i z grubsza jest ok, ale mam dwa dodatkowe przypadki, czyli

  • Pobranie historii rozmowy z danym użytkownikiem, tutaj sytuacja jest w miarę prosta, z bazy wyciągam wszelkie wiadomości między dwoma użytkownikami
    public List<MessageEntity> findMessagesBetween(String firstUser, String secondUser) {
        final var query = new Query(new Criteria().orOperator(messagesBetween(firstUser, secondUser), messagesBetween(secondUser, firstUser)));
        return mongoTemplate.find(query, MessageEntity.class);
    }

    private Criteria messagesBetween(String first, String second) {
        return new Criteria().andOperator(Criteria.where("sender").is(first), Criteria.where("receiver").is(second));
    }

Pozostaje wrzucenie tego w Set by pozbyć się duplikatów i zrobienie sortowania od najstarszej do najnowszej wiadomości.

  • Pobranie historii wszystkich rozmów, analogicznie do Messengera, widzę z kim ostatnio rozmawiałem i ostatnią wiadomość jaką wysłałem
public class ConversationSummary {
    private final String conversationWith;
    private final String lastMessage;
    private final LocalDateTime lastMessageAt;
}

Tutaj sprawa się nieco komplikuje, bo muszę :

  1. Wyciągnąć wszystkie wiadomości które zawierają id zalogowanego użytkownika
  2. Znaleźć w tych wiadomościach wszystkich odbiorców z którym dany użytkownik rozmawiał
  3. Zebrać wiadomości między każdym takim rozmówcą i użytkownikiem
  4. Znaleźć w każdym takim zbiorze najnowszą wiadomość
  5. Zebrać to do List<ConversationSummary> i posortować

I nawet to działa, ale zastanawiam się czy nie ma lepszego sposobu?

Drugie podejście o którym myślałem to zrobienie encji Conversation, w której mam wszystkie wiadomości między danymi dwoma użytkownikami. Taka encja byłaby tworzona by pierwszej wiadomości wysłanej między nimi a następnie byłaby modyfikowana. Problem jaki widzę - jednocześnie dwóch użytkowników modyfikuje tę samą encję na bazie, bo nowa wiadomość == dodanie czegoś do listy wiadomości wewnątrz tej encji Conversation. Dodatkowo, powiedzmy że Ci użytkownicy wymienili już kilka tysięcy wiadomości, wtedy wyciaganie tego za każdym razem z bazy i updateowanie może być kosztowne.

0

A gdyby każda wiadomość w ramach konwersacji miała numer sekwencji, np. int albo timestamp? Wtedy sortujesz wiadomości po stronie bazy i pobierasz tylko ogon sekwencji, a poprzednie wiadomosci możesz łatwo dociągać jako mające mniejszy numer sekwencji. Dodatkowo masz załatwione potwierdzenia przeczytania.

0

Zapisuje LocalDateTime, aczkolwiek timestamp może będzie lepszy. Nie do końca jednak rozumiem co dalej chcesz powiedzieć?

  • Do wyciągania całej rozmowy między użytkownikiem A i B, mogę zrobić sortowanie już po stronie bazy, to akurat trochę ułatwi. W sumie to samo mogłem zrobić z LocalDateTime, aczkolwiek pewnie jest to nieco cięższe
  • Do wyciągnięcia wszystkich konwersacji i ostatniej wiadomości w każdej z nich nadal musze wyciągać wszystkie wiadomości do / od użytkownika A, a nastepnie przejść przez sekwencje kroków opisanych w poprzednim poście.

Co do Dodatkowo masz załatwione potwierdzenia przeczytania. - tego już zdecydowanie nie łapie, a będę to potrzebować, więc jakbys mógł rozwinąć swoją wypowiedź to będę wdzięczny ;)

0

Mój pomysł polega na tym, żeby każda wiadomość miała inkrementowany numer sekwencji - int. Wtedy nie musisz wyciągać wszystkich wiadomości, tylko ostatnie, posortowane w Mongo po tym polu. Ostatnia wiadomość zawsze ma największy numer. Potwierdzenie przeczytania to byłby ostatni numer, jaki użytkownik wyświetlił. Na tej podstawie można sprawdzić czy użytkownik ma nieprzeczytane wiadomości i ile.
Ale może ktoś ma lepszy pomysł. :)

0

Ok, tyle że wtedy muszę jednak grupować te wiadomości w konwersację między użytkownikiem A i B? Sekwencja powinna byc unikalna dla takiej konwersacji, bo mogę mieć przykładowo:

{
	'sender' : 'Abacki',
	'receiver' : 'Babacki',
	'payload' : 'czesc'
	'seq' : 0
},
{
	'sender' : 'Babacki',
	'receiver' : 'Abacki',
	'payload' : 'no czesc, kope lat'
	'seq' : 1
},
...
{
	'sender' : 'Abacki',
	'receiver' : 'Cabacki',
	'payload' : 'gadalem ostatnio z Babackim'
	'seq' : 10
}

Nadal wtedy musze wyciągać wszytko gdzie sender || receiver == 'Abacki', a następnie szukać wszystkich unikalnych rozmówców i stworzyć kolekcję ich rozmów i dopiero z tej kolekcji mogę wyciągnąć max() filtrując po polu seq
Chyba, że czegoś nie rozumiem ;)

0

Wyjściem może być dodanie kolekcji z konwersacjami. Wiadomość miałaby dodatkowe ID konwersacji. Masz wtedy dwie kolekcje: Conversations i Messages. Za pomocą operacji Aggregate - Lookup na bazie dopasujesz wiadomości do konwersacji. W lookup pipeline sortujesz wiadomości malejąco według numeru sekwencji i ustawiasz limit, ile ostatnich pobrać :)

1 użytkowników online, w tym zalogowanych: 0, gości: 1