Skip to content

Commit 0def644

Browse files
authored
Added meeting scheduler low level design (#4)
* Added meeting scheduler low level design * Added mutex and Getroom * Add feedback and readme for meeting scheduler design * Update IsFree method for room
1 parent 54ce57b commit 0def644

File tree

9 files changed

+409
-0
lines changed

9 files changed

+409
-0
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Welcome to the **Low-Level System Design in Go** repository! This repository con
1111
- [Library Management System](#library-management-system)
1212
- [Vending Machine System](#vending-machine-system)
1313
- [Social Media Platform](#social-media-platform)
14+
- [Meeting Scheduler](#meeting-scheduler)
1415
- [Contributing Guidelines](#contributing-guidelines)
1516

1617
## Overview
@@ -99,6 +100,24 @@ The fifth project in this repository is a **Social Media Platform**. This system
99100
- **Error Handling**: Provides informative error messages for actions such as attempting to interact with nonexistent users or posts.
100101
- **Encapsulation of Features**: Each feature (users, posts, friendships) is encapsulated in a separate manager class, promoting modularity and ease of maintenance.
101102

103+
104+
## Meeting Scheduler
105+
106+
The sixth project in this repository is a Meeting Scheduler, a system that simulates meeting scheduling for users. It demonstrates the following concepts:
107+
108+
- Observer pattern for notifying users about scheduled meetings.
109+
- Book meeting for muliple users in particular time slot, so that room slot does not collide with each other.
110+
- Room calendar management, tracking when a room is booked or available.
111+
- Concurrency control, preventing scheduling conflicts in real time.
112+
113+
### Features
114+
115+
- Book and cancel meetings seamlessly.
116+
- Prevent double booking, allowing reservations only if the time slot is available.
117+
- Capacity-based booking, ensuring a room can only be booked if it has enough seats for all attendees.
118+
- Automated notifications to inform users about meeting bookings or cancellations.
119+
- Fetching all free rooms for a specific time slot.
120+
102121
## Contributing Guidelines
103122
Contributions to expand this repository with more low-level system design problems and solutions are welcomed. Here’s how you can contribute:
104123

meeting_scheduler/MeetingScheduler.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package main
2+
3+
import "errors"
4+
5+
type MeetingScheduler struct {
6+
rooms []*MeetingRoom
7+
meeting []*Meeting
8+
}
9+
10+
func NewMeetingScheduler() *MeetingScheduler {
11+
return &MeetingScheduler{
12+
rooms: []*MeetingRoom{},
13+
meeting: []*Meeting{},
14+
}
15+
}
16+
17+
func (ms *MeetingScheduler) GetRoom(roomId int) *MeetingRoom {
18+
for _, room := range ms.rooms {
19+
if room.id == roomId {
20+
return room
21+
}
22+
}
23+
return nil
24+
}
25+
26+
// book meeting for a given room
27+
func (ms *MeetingScheduler) BookMeeting(roomId int, meetingName string, participant []*User, host User, dur *interval, capacity int) (int, error) {
28+
29+
room := ms.GetRoom(roomId)
30+
if room == nil {
31+
return 0, errors.New("Room not found")
32+
}
33+
34+
err := room.BookRoom(capacity, dur)
35+
if err != nil {
36+
return 0, err
37+
}
38+
39+
meeting := NewMeeting(len(ms.meeting), meetingName, dur.id, room, &host)
40+
ms.meeting = append(ms.meeting, meeting)
41+
42+
meeting.AddParticipant(participant...)
43+
44+
return len(ms.meeting) - 1, nil
45+
}
46+
47+
func (ms *MeetingScheduler) CancelMeeting(meetingId int) error {
48+
if meetingId >= len(ms.meeting) || meetingId < 0 || ms.meeting[meetingId] == nil {
49+
return errors.New("There is no such meeting")
50+
}
51+
52+
// cancel meeting
53+
ms.meeting[meetingId].CancelMeeting()
54+
ms.meeting[meetingId] = nil
55+
56+
return nil
57+
}
58+
59+
// GetFreeRoom returns all rooms which are free in particular time interval
60+
func (ms *MeetingScheduler) GetFreeRoom(dur *interval) []*MeetingRoom {
61+
freeRooms := []*MeetingRoom{}
62+
for _, room := range ms.rooms {
63+
if room.IsFree(dur) {
64+
freeRooms = append(freeRooms, room)
65+
}
66+
67+
}
68+
69+
return freeRooms
70+
}

meeting_scheduler/calender.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"time"
6+
)
7+
8+
type interval struct {
9+
id int
10+
date time.Time
11+
start time.Time
12+
end time.Time
13+
status BookingStatus
14+
}
15+
16+
type Calendar struct {
17+
interval map[int]*interval
18+
}
19+
20+
func (c *Calendar) isFree(dur *interval) bool {
21+
// Check if the calendar is free for the given interval, you can add logic to better check if the interval is free
22+
for _, ci := range c.interval {
23+
// check if time is colliding
24+
if ci.status == BOOKED && ci.start.Before(dur.end) && ci.end.After(dur.start) {
25+
return false
26+
}
27+
}
28+
return true
29+
}
30+
31+
func (c *Calendar) setIntervalStatus(durId int, status BookingStatus) {
32+
c.interval[durId].status = status
33+
}
34+
35+
func (c *Calendar) bookInterval(dur *interval) error {
36+
if c.isFree(dur) {
37+
c.interval[dur.id] = dur
38+
c.setIntervalStatus(dur.id, BOOKED)
39+
return nil
40+
}
41+
42+
return errors.New("Interval is not free")
43+
}
44+
45+
func (c *Calendar) cancelInterval(durId int) error {
46+
if val, ok := c.interval[durId]; ok {
47+
c.setIntervalStatus(val.id, CANCEL)
48+
return nil
49+
}
50+
51+
return errors.New("Interval is not free")
52+
}

meeting_scheduler/go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module lld_meeting_scheduler
2+
3+
go 1.22.6
4+
5+
require github.com/pkg/errors v0.9.1

meeting_scheduler/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
2+
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

meeting_scheduler/main.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"time"
6+
)
7+
8+
// user code flow
9+
func main() {
10+
11+
// prerequisites before scheduling the meeting
12+
13+
// create rooms1
14+
room1 := &MeetingRoom{id: 1, capacity: 10, name: "Room 1", location: location{}, calendar: &Calendar{
15+
interval: make(map[int]*interval),
16+
}}
17+
18+
// create rooms2
19+
room2 := &MeetingRoom{id: 2, capacity: 10, name: "Room 2", location: location{}, calendar: &Calendar{
20+
interval: make(map[int]*interval),
21+
}}
22+
23+
// create user
24+
users := []*User{
25+
&User{id: 1, name: "sam", email: "[email protected]"},
26+
&User{id: 2, name: "ron", email: "[email protected]"},
27+
&User{id: 3, name: "don", email: "[email protected]"},
28+
&User{id: 4, name: "him", email: "[email protected]"},
29+
}
30+
31+
// first we will have already present rooms
32+
scheduler := NewMeetingScheduler()
33+
34+
scheduler.rooms = append(scheduler.rooms, []*MeetingRoom{room1, room2}...)
35+
36+
interval1 := &interval{
37+
id: 1,
38+
date: time.Date(2009, 11, 17, 0, 0, 0, 0, time.UTC),
39+
start: time.Date(2009, 11, 17, 1, 0, 0, 0, time.UTC),
40+
end: time.Date(2009, 11, 17, 1, 30, 0, 0, time.UTC)}
41+
42+
// prerequisites complete,Now we start the scheduling meeting, taking sam as host and ron as participant
43+
_, err := scheduler.BookMeeting(1, "Daily status", []*User{users[1], users[2]}, *users[0], interval1, 10)
44+
if err != nil {
45+
fmt.Printf("\nError while scheduling meeting:%#v", err)
46+
}
47+
48+
// lets try to book the same room again
49+
_, err = scheduler.BookMeeting(2, "Party", []*User{users[1], users[2]}, *users[0], interval1, 10)
50+
if err != nil {
51+
fmt.Printf("\nError while scheduling meeting:%#v", err)
52+
}
53+
54+
// lets try to book room with more capacity
55+
_, err = scheduler.BookMeeting(3, "Discussion", []*User{users[1], users[2]}, *users[0], interval1, 15)
56+
if err != nil {
57+
fmt.Printf("\nError while scheduling meeting:%#v", err)
58+
}
59+
60+
// book for other interval
61+
interval2 := &interval{
62+
id: 2,
63+
date: time.Date(2009, 11, 17, 0, 0, 0, 0, time.UTC),
64+
start: time.Date(2009, 11, 17, 2, 0, 0, 0, time.UTC),
65+
end: time.Date(2009, 11, 17, 2, 30, 0, 0, time.UTC)}
66+
67+
// lets try to book room with more capacity
68+
meetinIDx, err := scheduler.BookMeeting(1, "Discussion", []*User{users[1], users[3]}, *users[0], interval2, 3)
69+
if err != nil {
70+
fmt.Printf("\nError while scheduling meeting:%#v", err)
71+
}
72+
73+
// lets try to book room with more capacity
74+
_, err = scheduler.BookMeeting(1, "Discussion", []*User{users[1], users[3]}, *users[0], interval2, 3)
75+
if err != nil {
76+
fmt.Printf("\nError while scheduling meeting:%#v", err)
77+
}
78+
79+
// cancel the meeting and book again
80+
scheduler.CancelMeeting(meetinIDx)
81+
82+
// lets try to book room with more capacity
83+
_, err = scheduler.BookMeeting(1, "Discussion again", []*User{users[1], users[3]}, *users[0], interval2, 3)
84+
if err != nil {
85+
fmt.Printf("\nError while scheduling meeting:%#v", err)
86+
}
87+
}

meeting_scheduler/meeting.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"sync"
6+
)
7+
8+
type BookingStatus string
9+
10+
const (
11+
BOOKED BookingStatus = "booked"
12+
CANCEL BookingStatus = "cancel"
13+
)
14+
15+
type Meeting struct {
16+
id int
17+
name string
18+
room *MeetingRoom
19+
participant []*User
20+
host *User
21+
meetingDurationId int
22+
status BookingStatus
23+
mu sync.Mutex
24+
}
25+
26+
func NewMeeting(id int, name string, meetingDurationId int, room *MeetingRoom, host *User) *Meeting {
27+
return &Meeting{
28+
id: id,
29+
name: name,
30+
room: room,
31+
meetingDurationId: meetingDurationId,
32+
participant: []*User{},
33+
host: host,
34+
status: BOOKED,
35+
}
36+
}
37+
38+
func (m *Meeting) AddParticipant(u ...*User) {
39+
m.mu.Lock()
40+
defer m.mu.Unlock()
41+
m.participant = append(m.participant, u...)
42+
}
43+
44+
func (m *Meeting) RemoveParticipant(userId int) {
45+
m.mu.Lock()
46+
defer m.mu.Unlock()
47+
48+
// remove user from the participant list
49+
for i, user := range m.participant {
50+
if user.id == userId {
51+
m.participant = append(m.participant[:i], m.participant[i+1:]...)
52+
}
53+
}
54+
}
55+
56+
func (m *Meeting) setStatus(status BookingStatus) {
57+
m.status = status
58+
}
59+
60+
func (m *Meeting) CancelMeeting() {
61+
m.mu.Lock()
62+
defer m.mu.Unlock()
63+
64+
m.setStatus(CANCEL)
65+
m.room.CancelRoom(m.meetingDurationId)
66+
m.notifyParticipants(CANCEL)
67+
}
68+
69+
// notify use oberver pattern to notify all the participants
70+
func (m *Meeting) notifyParticipants(status BookingStatus) {
71+
fmt.Printf("\nMeeting %s has been %s\n", m.name, status)
72+
for _, user := range m.participant {
73+
user.notification()
74+
}
75+
}

0 commit comments

Comments
 (0)