Outra abordagem para construir arquitetura na frente

Boa tarde, queridos leitores. Neste artigo tentarei falar sobre o princípio de construção de uma arquitetura para o frontend, em particular para o React, uma vez que uma boa arquitetura deve ser um elemento independente do sistema.





No âmbito do artigo, tentarei simplesmente considerar e fornecer respostas aos seguintes tópicos:





  • o que é arquitetura e por que deveria ser limpa;





  • como escrever uma arquitetura baseada em serviços;





  • um exemplo de construção de uma arquitetura para uma aplicação de notas;





  • integração da arquitetura com o react.






Introdução

, . . , , , .





- , - .





- , , , . /, , -, , , ..





, , . , . .





- , . , , , .





OSAL (Operating System Abstraction Layer) - , HAL (Hardware Abstraction Layer), , . .





BSAL (Browser System Abstraction Layer). , , .





, , NodeJS.





. . , , .





, , - .





, , - . , - ( ), .





:





import { createBrowserHistory } from 'history';

class HistoryAPI {
    protected history = createBrowserHistory({});

    push(pathname: string): void {
        this.history.push(pathname);
    }
}

export default HistoryAPI;

      
      



, , , - . - . , .





- , , - . , .





- . , . , .





, . new



. , . , , .





:





const netAPI = new NetAPI();

const newNoteRepository = new NetNoteRepository(netAPI);

const noteService = new NoteService(newNoteRepository);
      
      



"", , , :





  1. - , . , JSON, , - ;





  2. - , , , ;





  3. ( ) - , , , , .. ;





  4. - , . . , , ..;





  5. - , , ;





  6. - "" , .





. , .





, , . , , .





, . .





, UML .





, JSON. . .





:





interface Note {
    id: string;
    name: string;
    description: string;
    created: number;
    tags: string[];
}

interface Filter {
    including: string;
    tags: string[];
}

interface User {
    name: string;
    role: 'user' | 'admin';
}
      
      



- , .





:





interface Factory {
    makeNote(): Note;
    makeFilter(): Filter;
}
      
      



, , : , - .





:





interface NoteHandler {
    note: Note;
    setName(name: string): void;
    setDescription(description: string): void;
    addTag(tag: string): void;
    removeTag(tag: string): void;
    validate(): Errors<Note>;
}
      
      



.





, API.





API

API , , , , ..





:





interface NetAPI {
    get<T>(url: string): Promise<T>;
    post<T, D>(url: string, data: D): Promise<T>;
}

      
      



API .





, axios, fetch , NetAPI



.





- , . , .





:





interface NoteRepository {
    loadNotes(filter: Filter): Promise<Note[]>;
    save(note: Note): Promise<boolean>;
}
      
      



, . , MockedNoteRepository



.





, , , .





,

, . , , - .





, , , .





:





interface Emmitable<E> {
    on<K extends keyof E>(event: K, cb: (event: E[K]) => void): void;
    off<K extends keyof E>(event: K, cb: (event: E[K]) => void): void;
    emit<K extends keyof E>(event: K, data: E[K]): void;
}

interface NoteEvents {
    change: undefined;
    notesChange: Note[];
    filterChange: Filter;
}

class NoteService implements Emmitable<NoteEvents> {
    noteRepository: NoteRepository;

    notes: Note[] = [];

    filter: Filter = {
        including: '',
        tags: [],
    }

    private callbacks: {
        [K in keyof NoteEvents]?: ((event: NoteEvents[K]) => void)[];
    } = {};

    
    on<K extends keyof NoteEvents>(event: K, cb: (event: NoteEvents[K]) => void): void {
        if (!this.callbacks[event]) {
            this.callbacks[event] = [];
        }
        const callbacks = this.callbacks[event];
        if (!Array.isArray(callbacks)) {
            return;
        }
        callbacks.push(cb);
    }

    off<K extends keyof NoteEvents>(event: K, cb: (event: NoteEvents[K]) => void): void {
        if (!this.callbacks[event]) {
            return;
        }
        const callbacks = this.callbacks[event];
        if (!Array.isArray(callbacks)) {
            return;
        }
        const index = callbacks.findIndex((aCallback) => aCallback === cb);
        if (index !== -1) {
            callbacks.splice(index, 1);
        }
    }

    emit<K extends keyof NoteEvents>(event: K, data: NoteEvents[K]): void {
        setTimeout(() => {
            if (!this.callbacks[event]) {
                return;
            }
            const callbacks = this.callbacks[event];
            if (!Array.isArray(callbacks)) {
                return;
            }
            callbacks.forEach((callback) => {
                callback(data);
            });
        }, 0);
    }

    constructor(noteRepository: NoteRepository) {
        this.noteRepository = noteRepository;
    }

    loadNotes(): Promise<boolean> {
        return this.noteRepository
            .loadNotes(this.filter)
            .then((notes) => {
                this.notes = notes;
                this.emit('notesChange', this.notes);
                this.emit('change', undefined);
                return true;
            });
    }

    saveNote(note: Note): Promise<boolean> {
        return this.noteRepository
            .save(note)
            .then(() => this.loadNotes());
    }

    setFilter(filter: Filter): void {
        this.filter = filter;
        this.emit('filterChange', this.filter);
        this.loadNotes();
    }
}

      
      



Emmitable<E>



, , .





, , - , . change



. , filterChange



.





- , - .





, MobX , , .





. , .





:





  1. , - ;





  2. - , .





:





-

- , - index.ts



. , API, . new



. , . Lego, .





:





const netAPI = new NetAPI();
const tokenGetter = new TokenGetter(netAPI);
const authNetAPI = new AuthNetAPI(tokenGetter);
const historyAPI = new HistoryAPI();

const services: Services = {
    note: new NoteService(new NoteRepository(authNetAPI)),
    modal: new ModalService(),
    page: new PageService(historyAPI),
    auth: new AuthService(new AuthRepository(netAPI)),
    user: new UserService(new UserRepository(authNetAPI)),
};

const application = new Application(services, tokenGetter, authNetRequest);
      
      



, .





, , .





:





function saveNote(services: Services, note: Note): void {
    services.note.saveNote(note)
        .then(() => {
            services.modal.setModal({
                type: 'success',
                title: '  ',
                description: '',
                onClose: () => {
                    services.modal.setModal(undefined);
                },
            });
            services.page.setPage({
                type: 'notes',
            });
        })
        .catch(() => {
            services.modal.setModal({
                type: 'error',
                title: '   ',
                description: '',
                onClose: () => {
                    services.modal.setModal(undefined);
                },
            });
        })
}
      
      



, . . :





class Scenarios {
    private services: Services;

    constructor(services: Services) {
        this.services = services;
    }

    saveNote(note: Note): void {
        // ...
    }
}
      
      



.





, - , index.ts



:





const root = document.getElementById('root');

ReactDOM.render(<App services={services} />, root);
      
      



, :





export default React.createContext<Services>({} as Services);
      
      



:





interface AppProps {
    services: Services;
}

const App: FC<AppProps> = ({ services }) => {
    return (
        <ServiceContext.Provider value={services}>
            {<AppContainer />}
        </ServiceContext.Provider>
    );
};
      
      



:





export default function useService<K extends keyof Services>(service: K): Services[K] {
    const services = useContext(ServiceContext);
    return services[service];
}
      
      



:





const NotesPage: FC = () => {
    const noteService = useService('note');

    const [notes, setNotes] = useState<Note[]>(noteService.notes);

    useEffect(() => {
        const onChange = () => {
            setNotes(noteService.notes.concat());
        };

        noteService.on('change', onChange);

        return () => {
            noteService.off('change', onChange);
        };
    }, [noteService]);
    // ...
}
      
      



, :





  • - , , ;





  • - , , .





, .





, , .





, . , .





, , . - .





, , : , . , , .





O exemplo considerado de construção de um aplicativo mostra apenas recomendações e uma abordagem para construir sua arquitetura. Portanto, sua arquitetura deve ser sua e depender do próprio significado da aplicação.








All Articles