Boochat is a chat and meetups application developed as a hobby for my friends. This repository refers to the server implementation monorepo. For the ui repository check here.
The project is inspired by ideas from Event-driven Architecture and Event Sourcing.
- Make sure you have docker installed on your machine, with docker-compose. Install guide
- Create two copies of the file
.env.example
and name them.env
and.local.env
. The local env file should have the variables that affect the local serve of the app. The already existing .docker.env file relates to the docker stack and its variables. Τhe.env
file should have the variables that affect the release app. The .env.example file is a valid .docker.env file, so that should work out of the box. - The app has the potential serve push notifications to android devices leveraging Firebase Cloud messaging. Follow the instruction on how to set this up here and remember to update the respective .env files with
GOOGLE_APPLICATION_CREDENTIALS
. If you just want to run on web, don't worry this step is not mandatory. - Run
docker-compose up -d
inside the root directory of the project. - For watching logs, run
docker-compose logs --follow
You should a working local version of the whole stack.
- NestJs - Opinionated Node.js framework for building server-side applications.
- MongoDB - Classified as a NoSQL database program, MongoDB uses JSON-like documents with optional schemas.
- RabbitMQ - RabbitMQ is the most widely deployed open source message broker.
Following Domain-Driven-Design principles, there will be a description of the entities, events, commands and queries.
- Everything in Bold reffers to Entities
- Everything in Italic reffers to Events
A User is anyone who interacts with the UI of the app.
- id: string (got from Google Authentication)
- name: string
- imageUrl: string
The room is the actual chat room in which people exchange Messages. A room has participats(in the form of Users) that might change as time progresses, since Users get Invited or Leave Rooms.
- name: string
- participants: User Array
- items: RoomItem Array
- imageUrl: string
A RoomItem encapsulates two entities, Messages and Announcements. Announcements are stuff like "User left a room", "User was invited to a room", "User created a poll". Messages are typical data sent by a specific user. Announcements are sent by the system and that's their core difference.
This is probably the most business important entity of the system even though it's contents are self-explained.
- sender: User
- content: string
- dateSent: Date
- room: Room
- content: string
- timestamp: Date
- roomId: string
A Meetup describes a gathering to take place on a specific Date, by specific attendees, at a specific location. A Meetup is always related with a Room.
- name :string
- organizer: User
- location: string
- attendants: User Array
- takesPlaceOn: Date
- room: Room
- polls: Poll array
- alert: Alert array
An alert typically appears in a room window and it relates to its connected meetup. Below is the alert enum which will explain what an alert might be
enum AlertEnum {
PENDING_POLL,
PENDING_RESCHEDULE_POLL,
PENDING_RELOCATION_POLL
}
type: AlertEnum payload: unknown
enum PollTypeEnum {
GENERIC_POLL,
RESCHEDULE_POLL,
RELOCATE_POLL
}
- participantIds: string array
- type: PollTypeEnum
- status: PollStatusEnum
- votes: PollVote array
- creatorId: string (user's google id)
- meetupId: string
- dateCreated: string
- description: string
- pollChoices: string array
A poll vote has to be an independent entity in the database because it triggers workflows. For example if a user casts the last poll vote, the poll might close.
- userId: string
- choiceIndex: number
- pollId: string
Every Event has a userId
property and a type
property. The ```type`` property is a enum lookup. Every event tends to have a userId since most events have a creator. A Message's creator is the sender. An announcement's creator is kind of implied. For example when a Room Announcenent is created about a new user invitation, the creator is the inviter.
Events are seperated into three categories:
- Room Events
- Meetup Events
- User Events
Event Name | Payload |
---|---|
Room created | User Created Room Payload |
Message sent | Send Message Payload |
*Announcement Created * | Announcement Created Payload |
User closed Room | User Closed Room Payload |
User invited User to room event | User Invited to Room |
User leftRoom** | User Left Room Paylod |
enum RoomEventEnum {
ROOM_CREATED = 1,
USER_INVITED_ROOM,
USER_LEFT_ROOM,
USER_CLOSED_ROOM,
USER_SENT_MESSAGE,
ANNOUNCEMENT_CREATED
}
A User (Event Name) | Payload |
---|---|
User created a Meetup | User Created Meetup Payload |
Poll closed | Poll Closed Payload |
User Changed Meetup Image closed | User Changed Meetup Payload |
User changed rsvp | User Changed Rsvp Payload |
created Poll | User Created Poll Payload |
cast Poll vote | User Cast Poll Vote Payload |
enum MeetupEventEnum {
MEETUP_CREATED = 1,
USER_CHANGED_RSVP,
USER_CREATED_POLL,
USER_VOTED_ON_POLL,
USER_CHANGED_MEETUP_IMAGE,
POLL_CLOSED
}
Event Name) | Payload |
---|---|
authenticated | User Authenticated Payload |
closed room | User Closed Room Payload |
enum ApplicationEventEnum {
USER_CONNECTED = 1,
USER_DISCONNECTED = 2
}
- userId: string
- roomName: string
- userIds: string[]
- imageUrl: string
- senderId: string
- roomId: string
- content: string
- senderId: string
- roomId: string
- content: string
- userId: string
- roomId: string
- timestamp: Date
- userId: string;
- inviteeId: string;
- roomId: string;
- userId: string;
- roomId: string;
- userId: string
- imageUrl: string
- email: string // name should be inside .env for allowing users to auth to the app
- name: string
- userId: string
- roomId: string
- timestamp: Date
- userId: string
- name: string
- attendees: string array
- location: string
- organizerId: string
- takesPlaceOn: Date
- imageUrl: string
- roomId: string
- userId: string
- meetupId: string
- pollId:string
- userId: string
- meetupId: string
- pollId:string
-userId: string
- meetupId:string
- pollType: PoleTypeEnum
- description:string
- pollChoices: string array
- userId: string
- meetupId: string
- rsvp: number
- userId: string,
- meetupId: string,
- pollType: PollType
- description: string,
- pollChoices: string[]
- userId: string
- pollId: string
- pollChoiceIndex: number
Even though the application mainly works asynchronously via RabbitMQ and WebSockets, there some Rest Endpoints.
- The main authentication endpoint resides in the command-api layer. The user grabs an encrypted JWT token that each service knows how to decode by sharing a secret. The encryption is done by the native nodjs encryption libraries.
- When a user clicks a room, the user gets all the roomItems of that specific room. This is an endpoint that resides in query-api.
- Create Persistence Layer
- Create Event Store
- Create Domain Events
- Handle Domain Events in Application Layer
- Save events to Event Store
- Emit Event to Message Broker(RabbitMQ)
- Create Persitence Layer
- Finalize Database Entities
- Create Database Architecture for saving denormalized events
- Req/Res Layer
- Create Queries and Query Handlers
- Web Socket Layer
- Emit Web Socket messages after consuming events from Message Broker