Compare commits

..

2 Commits

Author SHA1 Message Date
ef10264e8a add synch policy 2022-06-12 01:05:00 +09:00
8c95d2f24f enhance serverside uml 2022-06-11 23:50:11 +09:00
2 changed files with 274 additions and 33 deletions

View File

@ -6,22 +6,31 @@
```mermaid
classDiagram
class PermissionDescriptor {
class IPermissionDescriptor {
+canRead(path: string): boolean
+canWrite(path: string): boolean
+canCustom(path: string, options: any): boolean
+toJSON(): any
}
<<Interface>> PermissionDescriptor
PermissionDescriptor <|.. PermissionImpl
<<Interface>> IPermissionDescriptor
IPermissionDescriptor <|.. AdminPermission
class AdminPermission {
+canRead(): boolean
+canWrite(): boolean
+canCustom(): boolean
+toJSON(): Inline
}
IPermissionDescriptor <|.. PermissionImpl
class PermissionImpl {
+basePath: string
+writable: boolean
+toJSON(): Inline
+canRead(path: string): boolean
+canWrite(path: string): boolean
+canCustom(_path: string, _options: any): boolean
}
SessionStore o-- UserSession
UserSession *-- PermissionDescriptor
UserSession *-- IUser
class SessionStore~T~ {
+sessions: Record<string, T>
+get(id: string): T
@ -34,9 +43,64 @@ class UserSession {
+id: string
+superuser: boolean
+expiredAt: number
+permissionSet: PermissionDescriptor
+permissionSet: IPermissionDescriptor
}
<<Interface>> UserSession
IPermissionDescriptor <|.. IUser
class IUser {
+id: string
+expiredAt: number
+basepath: string
+joinPath(path: string): string
+relativePath(path: string): string
+setExpired(seconds: number): void
+isExpired(): boolean
+toJSON(): any
}
<<Interface>> IUser
IUser <|.. UserSessionImpl
class UserSessionImpl {
+id: string
+expiredAt: number
+permissionSet: IPermissionDescriptor
+basepath: string
+toJSON(): UserSessionImplJSON
+joinPath(path: string): string
+relativePath(path: string): string
+setExpired(seconds: number): void
+isExpired(): boolean
+canRead(path: string): boolean
+canWrite(path: string): boolean
+canCustom(path: string, options: any): boolean
}
```
```mermaid
classDiagram
class DocumentContent {
+chunks: RPC.Chunk[]
+tags: string[]
+version: number
}
<<Interface>> DocumentContent
class DocReadWriter {
+read(path: string): Promise<DocumentContent>
+save(path: string, doc: DocumentContent): Promise<void>
}
<<Interface>> DocReadWriter
DocReadWriter <|.. MemoryDocReadWriterType
class MemoryDocReadWriterType {
-store: Map<string, DocumentContent>
+read(path: string): Promise<DocumentContent>
+save(path: string, doc: DocumentContent): Promise<void>
+clear(): void
}
DocReadWriter <|.. DocFileReadWriterType
class DocFileReadWriterType {
+rw: IReadWriter
+read(path: string): Promise<DocumentContent>
+save(path: string, doc: DocumentContent): Promise<void>
}
DocumentContent ..> DocReadWriter
```
```mermaid
classDiagram
@ -80,11 +144,20 @@ Router <|.. FsRouter
classDiagram
class ResponseBuilder {
+status: Status
+headers: Record<string, string>
+headers: Headers
+body?: BodyInit
+#response: Response
+#resolved: boolean
+resolved: boolean
+setStatus(status: Status): this
+setHeader(key: string, value: string): this
+setHeaders(headers: Record<string, string>): this
+setBody(body: BodyInit): this
+setResponse(response: Response, resolved?: boolean): this
+setCors(origin: string, credentials: boolean): this
+setCorsMethods(methods: string[]): this
+setContentType(contentType: string): this
+setJson(json: unknown): this
+redirect(location: string): this
+build(): Response
}
@ -133,8 +206,10 @@ class ChunkMoveAction {
classDiagram
class Participant {
+id: string
+user: UserSession
+user: IUser
+send(data: string): void
+sendNotification(notification: RPC.RPCNotification): void
+responseWith(data: RPC.RPCResponse): void
+addEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void
+removeEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void
+close(): void
@ -143,8 +218,10 @@ class Participant {
Participant <|.. Connection
class Connection {
+id: string
+user: UserSession
+user: IUser
+socket: WebSocket
+sendNotification(notification: RPC.RPCNotification): void
+responseWith(res: RPC.RPCResponse): void
+send(data: string): void
+addEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void
+removeEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void
@ -156,33 +233,50 @@ class ParticipantList {
+get(id: string): any
+remove(id: string): void
+unicast(id: string, message: string): void
+broadcast(message: string): void
+unicastNotification(id: string, notification: RPC.RPCNotification): void
+broadcastNotification(notification: RPC.RPCNotification): void
}
ParticipantList o-- Participant
```
```mermaid
classDiagram
DocumentObject <|.. FileDocumentObject
class FileDocumentObject {
+docPath: string
+chunks: Chunk[]
+tags: string[]
+updatedAt: number
+tagsUpdatedAt: number
+open(): Promise<void>
+parse(content: unknown[]): void
+save(): Promise<void>
class ISubscriptable {
+join(participant: Participant): void
+leave(participant: Participant): void
+broadcastChunkMethod(method: ChunkNotificationParam, updatedAt: number, exclude?: Participant): void
+participantsCount: number
+participants: Participant[]
}
FileDocumentObject <|-- ActiveDocumentObject
<<Interface>> ISubscriptable
ISubscriptable <|.. ActiveDocumentObject
class ActiveDocumentObject {
+conns: Set<Participant>
-conns: RefCountSet<Participant>
-disposeHandlers: (() => void)[]
+history: DocHistory[]
+maxHistory: number
+docPath: string
+chunks: RPC.Chunk[]
+updatedAt: number
+seq: number
+#tags: string[]
+tagsUpdatedAt: number
+readWriter: DocReadWriter
+dispose(): void
+save(): Promise<void>
+setTags(tags: string[]): void
+tags: string[]
+participantsCount: any
+participants: Participant[]
+join(conn: Participant): void
+joined(conn: Participant): boolean
+leave(conn: Participant): void
+open(): Promise<void>
+updateDocHistory(method: ChunkMethodHistory): void
+broadcastMethod(method: ChunkMethod, updatedAt: number, exclude?: Participant): void
+broadcastChunkMethod(method: ChunkNotificationParam, updatedAt: number, exclude?: Participant): void
+broadcastTagsNotification(exclude?: Participant): void
}
IDisposable <|.. ActiveDocumentObject
DocumentObject <|.. ActiveDocumentObject
class DocumentStore {
+documents: Inline
+open(conn: Participant, docPath: string): Promise<ActiveDocumentObject>
@ -193,16 +287,91 @@ DocumentStore o-- ActiveDocumentObject
```
```mermaid
classDiagram
class IFsWatcher{
addEventListener(): void
onNofity(e: FileWatchEvent): void
class IDisposable {
+dispose(): void
}
<<interface>> IFsWatcher
class FsWatcherImpl{
onNotify(e: FileWatchEvent): void
<<Interface>> IDisposable
IDisposable <|.. RefCountDisposable
class RefCountDisposable {
-refCount: number
-handlers: (() => void)[]
+dispose(): void
+disposeForced(): void
+addRef(): void
+addDisposeHandler(handler: () => void): void
}
class RefCountSet~T~ {
-items: Map<T, RefCountDisposable>
+add(item: T, disposeHandler?: () => void): void
+addDisposeHandler(item: T, handler: () => void): boolean
+delete(item: T): void
+deleteForced(item: T): void
+clear(): void
+size: number
+has(item: T): boolean
+values(): IterableIterator<T>
}
RefCountDisposable --o RefCountSet~T~
```
```mermaid
classDiagram
Event <|-- FsWatcherEvent
class FsWatcherEvent {
+paths: string[]
}
EventTarget <|-- FsWatcher
class FsWatcher {
-path: string
-watcher?: Deno.FsWatcher
-filterFns: ((path: string, kind: FsWatchEventType) => boolean)[]
+addFilter(fn: (path: string, kind: FsWatchEventType) => boolean): void
+removeFilter(fn: (path: string, kind: FsWatchEventType) => boolean): boolean
+startWatching(): void
+stopWatching(): void
+addEventListener(type: FsWatchEventType, handler: (e: FsWatcherEvent) => void): void
}
```
```mermaid
classDiagram
class IReadWriter {
+read(path: string): Promise<string>
+write(path: string, content: string): Promise<void>
}
<<Interface>> IReadWriter
class MemoryReadWriter {
-data: Record<string, string>
+read(path: string): Promise<string>
+write(path: string, content: string): Promise<void>
}
IReadWriter <|.. MemoryReadWriter
IReadWriter <|.. AtomicReadWriter
class AtomicReadWriter {
+read(path: string): Promise<string>
+write(path: string, content: string): Promise<void>
}
IReadWriter <|.. WatchFilteredReadWriter
class WatchFilteredReadWriter {
-raw: AtomicReadWriter
-fsWatcher: FsWatcher
+read(path: string): Promise<string>
+write(path: string, content: string): Promise<void>
}
IReadWriter <|.. QueueReadWriter
class QueueReadWriter {
-queue: Command[]
+started: boolean
-waitedResolve: () => void
+delayCount: number
+baseReadWriter: IReadWriter
+read(path: string): Promise<string>
+write(path: string, content: string): Promise<void>
+save(path: string, content: string): void
+startTimer(): void
+flush(): Promise<void>
+wait(): Promise<void>
}
```
### 5.1.2 Client Side UML
```mermaid
@ -608,6 +777,7 @@ fn apply(this, mutator, updatedAt, seq){
### 5.2.3 다른 작업들
다음은 청크 컴포넌트에 관한 의사코드이다.
```
module chunk {
type mode = Read | Write
@ -648,7 +818,7 @@ module chunk {
}
}
```
다음은 검색 기능에 관한 의사코드이다.
```
module search {
searchWord(chunks, word) {
@ -670,7 +840,7 @@ module search {
when Ctrl-F is pressed { searchWordPrompt() }
}
```
다음은 문서 컴포넌트에 관한 의사코드이다.
```
module document {
struct Document {
@ -703,7 +873,7 @@ module document {
}
}
```
다음은 파일리스트 컴포넌트에 관한 의사코드이다.
```
module filelist {
fileList(dir : Directory, open: (File) => void) : Component {
@ -721,7 +891,7 @@ module filelist {
}
}
```
다음은 설정에 관한 의사코드이다.
```
module settings {
settings() : Component {
@ -742,6 +912,7 @@ module settings {
}
}
```
다음은 프론트엔드를 요약하는 의사코드이다.
```
module frontend {
main() : Component {
@ -764,3 +935,69 @@ module frontend {
}
}
```
## 5.3 동기화 정책(Synchronization Policy)
동기화 정책은 이렇다. 기본적으로 상대문서의 갱신 시간과 나의 갱신 시간을 비교하고 상대문서가 최신이 아니면 거부한다. 메소드가 실행 순서에
무관하면 언제나 실행하고 갱신한다. 메소드가 순서에 따라 조정될 수 있으면 조정한다.
예를 들어, 다음과 같은 상황이 있다.
```mermaid
sequenceDiagram
participant A as Alice
participant S as Server
participant B as Bob
S ->> A: document.open
S ->> B: document.open
A ->> S: chunk.create
B ->> S: chunk.create
S -->> A: response
S -->> B: conflict
S -) B: chunk.update
```
처음에 Server에서 Alice와 Bob이 문서를 받아왔다. 그리고 Alice가 `chunk.create` 메소드를 보내고 Bob이
`chunk.create`메소드를 보냈다. 그러면 Server는 Alice가 먼저 도착했으므로 Alice의 메소드을 처리하고 최근 갱신 시간을
갱신한다. 그리고 Bob의 메소드를 처리할때 Bob의 문서의 최근 갱신 시간이 Server 문서의 최근 갱신 시간보다 작으므로 거부한다.
별개로 Alice의 갱신사실을 알리기 위해서 Bob에게 `chunk.update`를 보낸다.
다른 예로 다음과 같은 상황에서는 이렇다.
```mermaid
sequenceDiagram
participant A as Alice
participant S as Server
participant B as Bob
S ->> A: document.open
A ->> S: chunk.create
S ->> B: document.open
S -->> A: response
S -) B: chunk.update
B ->> S: chunk.create
S -->> B: response
S -) A: chunk.update
```
여기서는 Bob의 문서가 최신이기 때문에(**문서 갱신 시간이 Server와 같기 떄문에**) 거부되지 않고 처리되는 것을 볼 수 있다.
그와 별개로 갱신 사실을 알리기 위해서 `chunk.update`가 Alice와 Bob에게 전달되는 것을 볼 수 있다.
메소드를 조정할 수 있으면 조정한다는 것은 인수를 바꾸어서 실행한다는 것이다. 예를 들어 다음의 예에 대해서
```mermaid
sequenceDiagram
participant A as Alice
participant S as Server
participant B as Bob
S ->> A: document.open
S ->> B: document.open
A ->> S: chunk.create
B ->> S: chunk.create
S -->> A: response
S -->> B: response
S -) B: chunk.update
```
A는 1번째 위치에서 `chunk.create`에서 시도하고 B가 3번째 위치에서 `chunk.create`를 시도하면 A가 B보다 먼저 앞의 위치에서
삽입을 했으므로 B의 위치를 4번째로 조정하고 적용한다.

View File

@ -47,6 +47,10 @@
1. [Server Side UML](./architecture.md#511-server-side-uml)
2. [Client Side UML](./architecture.md#512-client-side-uml)
2. [의사코드(Pseudo Code)](./architecture.md#52-의사코드pseudo-code)
1. [서버 RPC 메세지 처리](./architecture.md#521-서버-rpc-메세지-처리)
2. [클라이언트의 메세지 처리 동기화](./architecture.md#522-클라이언트의-메세지-처리-동기화)
3. [다른 작업들](./architecture.md#523-다른-작업들)
3. [동기화 정책(Synchronization Policy)](./architecture.md#53-동기화-정책synchronization-policy)
6. [시험(Testing)](./testing.md)
1. [유닛 테스트(Unit test)](./testing.md#61-유닛-테스트unit-test)
2. [기능 테스트(Functional Test)](./testing.md#62-기능-테스트functional-test)