jasondchambers / netorganizer

Network Organizer enables you to bring some order to the chaos that might be your network. It enables you to take inventory of active hosts and to neatly classify each of them taking care of fixed IP reservations. It can bring to your attention un-classified hosts that are actively on your network and invite you to classify them.
Apache License 2.0
2 stars 0 forks source link

Research Hexagonal Architecture #28

Open yzxbmlf opened 1 year ago

yzxbmlf commented 1 year ago

The idea of ports and adapters I feel provides an opportunity to clean up the structure and make it easier to expand support beyond Meraki and also enable different user interfaces beyond the command line. Also, as I think about classification I want to enable classifiers to be easily plugged in. The first classifier I have in mind is a simple manual classifier.

The other key benefit I think is testability. The problem with lots and lots of unit tests is it makes the actual code harder and longer to refactor. They are still valuable and have their place, but I want to be able to easily re-factor the core of Net Organizer without a) breaking things but also b) not having to refactor a ton of tests too. I think this video provides a good foundation https://www.youtube.com/watch?v=EZ05e7EMOLM

https://alistair.cockburn.us/hexagonal-architecture/

yzxbmlf commented 1 year ago
Screen Shot 2022-08-08 at 1 02 37 PM
yzxbmlf commented 1 year ago

Quick hack to demonstrate the concept. The value here is that the core NetOrganizerApp doesn't know or care how data is fed into it (input ports) and it doesn't care what happens downstream (output ports. This makes the architecture highly testable (by providing alternative adapters) and flexible (new adapters can be added/plugged-in).

from typing import NamedTuple
from abc import ABC, abstractclassmethod, abstractmethod
from typing import List

class KnownDevice(NamedTuple):
    name: str
    mac: str
    group: str

    def __str__(self):
        return f'{self.name} with MAC {self.mac} in {self.group}'

class KnownDevicesPort(ABC):

    @abstractmethod
    def load() -> List[KnownDevice]:
        pass

    @abstractmethod
    def save(list_of_known_devices: List[KnownDevice]) -> None:
        pass

class KnownDevicesYamlFileAdapter(KnownDevicesPort):

    def load(self) -> List[KnownDevice]:
        print("Loading from file")
        jasons_ipad = KnownDevice(name="Jason's iPad", mac="10:ab:cc", group="jasons_devices")
        list_of_known_devices: List[KnownDevice] = []
        list_of_known_devices.append(jasons_ipad)
        return list_of_known_devices

    def save(self,list_of_known_devices: List[KnownDevice]) -> None:
        print("Saving to file ")
        for known_device in list_of_known_devices:
            print (known_device)

class NetOrganizerApp():

    def __init__(self, known_devices_port: KnownDevicesPort) -> None:
        self.known_devices_port = known_devices_port

    def do_something(self) -> None:
        list_of_known_devices = self.known_devices_port.load()
        self.known_devices_port.save(list_of_known_devices)

known_devices_port = KnownDevicesYamlFileAdapter()
net_organizer_app = NetOrganizerApp(known_devices_port=known_devices_port)
net_organizer_app.do_something()