ตัวอย่างการออกแบบ Class ใน Python สำหรับ Network Automation

Python Class Designing for Network Automation

Nopnithi Khaokaew (Game)
4 min readJun 25, 2020

— — — — — — — — — — — — — — —
สารบัญเนื้อหาทั้งหมด (My Contents)
— — — — — — — — — — — — — — —

ปกติแล้ว software design principles หรือหลักการในการออกแบบซอฟต์แวร์ที่เค้านิยมใช้กันคือ SOLID ซึ่งมีทั้งหมด 5 ตัว ได้แก่

  1. Single Responsibility Principle
  2. Open-closed principle
  3. Liskov’s substitution principle
  4. Interface segregation principle
  5. Dependency inversion principle

ซึ่งบทความนี้ผมจะมาลองออกแบบ class ให้กับ automation script ของผมดู

Single Responsibility Principle (SRP)

1 class ทำงานแค่ 1 หน้าที่

ตัวอย่าง 1 (SRP)

class Infrastructure:
def __init__(self, name):
self.name = name
def deploy(self, service, config):
print(f'Deploy service: {service}')
print(f'On infrastructure: {self.name}')
print(f'From config: {config}')
class NetworkService:
def __init__(self, name, config):
self.name = name
self.config = config
def set_name(self, name):
self.name = name
def set_config(self, config):
self.config = config
def deploy_service(self, infra):
Infrastructure(infra).deploy(self.name, self.config)
if __name__ == '__main__':
ntp = NetworkService('NTP', 'ntp_config.txt')
ntp.deploy_service('Production')

Output:

Deploy service: NTP
On infrastructure: Production
From config: ntp_config.txt

จะเห็นว่า NetworkService class ทำหน้าที่ถึง 2 อย่าง

  1. เซ็ตค่า parameter ของ service
  2. ทำการ deploy service

ปัญหาก็คือถ้าเราต้องเปลี่ยนในส่วน deploy ขึ้นมา มันจะมากระทบกับ NetworkService class ด้วย ดังนั้นเราจะแยกมันออกจากกัน

ตัวอย่าง 2 (SRP)

class Infrastructure:
def __init__(self, name):
self.name = name
def deploy(self, service, config):
print(f'Deploy service: {service}')
print(f'On infrastructure: {self.name}')
print(f'From config: {config}')
class NetworkService:
def __init__(self, name, config):
self.name = name
self.config = config
def set_name(self, name):
self.name = name
def set_config(self, config):
self.config = config
class NetworkServiceDeployer:
@staticmethod
def deploy_service(service, infra):
Infrastructure(infra).deploy(service.name, service.config)

if __name__ == '__main__':
ntp = NetworkService('NTP', 'ntp_config.txt')
NetworkServiceDeployer().deploy_service(ntp, 'Production')

Output:

Deploy service: NTP
On infrastructure: Production
From config: ntp_config.txt

คราวนี้ผมเพิ่ม NetworkServiceDeployer class ขึ้นมาเพื่อทำหน้าที่ในส่วนของการ deploy service ต่างหาก ดังนั้นเราสามารถเปลี่ยนแปลงแก้ไข code ในแต่ละส่วนได้โดยไม่กระทบกันแล้ว

ทีนี้มันจะมีท่าที่เค้านิยมใช้กับ single responsibility principle แบบนี้

ตัวอย่าง 3 (SRP)

class Infrastructure:
def __init__(self, name):
self.name = name
def deploy(self, service, config):
print(f'Deploy service: {service}')
print(f'On infrastructure: {self.name}')
print(f'From config: {config}')
class NetworkService:
def __init__(self, name, config):
self.name = name
self.config = config
self.deployer = NetworkServiceDeployer()
def set_name(self, name):
self.name = name
def set_config(self, config):
self.config = config
def deploy_service(self, infrastructure):
self.deployer.deploy_service(self, infrastructure)
class NetworkServiceDeployer:
@staticmethod
def deploy_service(service, infra):
Infrastructure(infra).deploy(service.name, service.config)
if __name__ == '__main__':
ntp = NetworkService('NTP', 'ntp_config.txt')
ntp.deploy_service('Production')

Output:

Deploy service: NTP
On infrastructure: Production
From config: ntp_config.txt

จะเห็นว่า pattern นี้ยังคงรักษาความเป็น single responsibility principle อยู่ แต่ในขณะเดียวกันก็ทำให้การ interface นั้นง่ายขึ้นด้วย

Open-closed Principle (OCP)

เพิ่ม featureใหม่โดยไม่ต้องเปลี่ยนส่วนอื่น ๆ

จากตัวอย่างด้านล่างจะเห็นว่า NetworkServiceDeployer class ถูกใช้เพื่อ deploy service ใน network type ที่แตกต่างกัน และในตอนนี้ support ใน type ที่เป็น user อยู่ แต่จะเกิดอะไรขึ้นถ้าเราต้องการเพิ่มส่วนที่เป็น mgmt ด้วย?

ตัวอย่าง 4 (SRP + before OCP)

class Infrastructure:
def __init__(self, name):
self.name = name
def deploy(self, service, config, segment):
print(f'Deploy service: {service}')
print(f'On infrastructure: {self.name}')
print(f'In segment: {segment}')
print(f'From config: {config}')
class NetworkService:
def __init__(self, name, network_type, config):
self.name = name
self.config = config
self.network_type = network_type
self.deployer = NetworkServiceDeployer()
def deploy_service(self, infra):
self.deployer.deploy_service(self, infra)
class NetworkServiceDeployer:
@staticmethod
def deploy_service(service, infra):
if service.network_type == 'user':
Infrastructure(infra).deploy(
service.name, service.config, '10.0.1.0/24')

if __name__ == '__main__':
ntp = NetworkService('NTP', 'user', 'ntp_config.txt')
ntp.deploy_service('Production')

Output:

Deploy service: NTP
On infrastructure: Production
In segment: 10.0.1.0/24
From config: ntp_config.txt

ถูกต้องครับ เราจะต้องแก้ code ใน NetworkServiceDeployer class เพื่อเพิ่ม feature สำหรับ mgmt ซึ่งละเมิดกฎของ open-closed principle นั่นเอง ลองดูตัวอย่างด้านล่างนี้ครับ

ตัวอย่าง 5 (SRP + before OCP)

class Infrastructure:
def __init__(self, name):
self.name = name
def deploy(self, service, config, segment):
print(f'Deploy service: {service}')
print(f'On infrastructure: {self.name}')
print(f'In segment: {segment}')
print(f'From config: {config}')
class NetworkService:
def __init__(self, name, network_type, config):
self.name = name
self.config = config
self.network_type = network_type
self.deployer = NetworkServiceDeployer()
def deploy_service(self, infra):
self.deployer.deploy_service(self, infra)
class NetworkServiceDeployer:
@staticmethod
def deploy_service(service, infra):
if service.network_type == 'user':
Infrastructure(infra).deploy(
service.name, service.config, '10.0.1.0/24')
elif service.network_type == 'mgmt':
Infrastructure(infra).deploy(
service.name, service.config, '10.0.2.0/24')

if __name__ == '__main__':
ntp1 = NetworkService('NTP', 'user', 'ntp_config.txt')
ntp1.deploy_service('Production')
print('-' * 30)
ntp2 = NetworkService('NTP', 'mgmt', 'ntp_config.txt')
ntp2.deploy_service('Test')

Output:

Deploy service: NTP
On infrastructure: Production
In segment: 10.0.1.0/24
From config: ntp_config.txt
------------------------------
Deploy service: NTP
On infrastructure: Test
In segment: 10.0.2.0/24
From config: ntp_config.txt

แต่เพื่อให้สอดคล้องกับ open-closed principle นั้น code ในส่วนของ NetworkService class และ NetworkServiceDeployer class จะต้องไม่เปลี่ยนแปลงแม้จะมีการเพิ่ม feature ใหม่เข้ามา

ซึ่งเราจะใช้วิธีการ inherit class มาจาก class อื่นเพื่อสร้างเป็น class ใหม่ที่มีลักษณะเหมือน class เดิมทุกประการ เพียงแต่สามารถเพิ่ม parameter หรือ method ใหม่ ๆ เข้าไปได้

ตัวอย่าง 6 (SRP + OCP)

class Infrastructure:
def __init__(self, name):
self.name = name
def deploy(self, service, config, segment):
print(f'Deploy service: {service}')
print(f'On infrastructure: {self.name}')
print(f'In segment: {segment}')
print(f'From config: {config}')
class NetworkService:
def __init__(self, name, config):
self.name = name
self.config = config
class NetworkServiceUser(NetworkService):
def __init__(self, name, config):
super().__init__(name, config)
self.segment = '10.0.1.0/24'
self.deployer = NetworkServiceDeployer()
def deploy_service(self, infra):
self.deployer.deploy_service(self, infra)
class NetworkServiceMgmt(NetworkService):
def __init__(self, name, config):
super().__init__(name, config)
self.segment = '10.0.2.0/24'
self.deployer = NetworkServiceDeployer()
def deploy_service(self, infra):
self.deployer.deploy_service(self, infra)
class NetworkServiceDeployer:
@staticmethod
def deploy_service(service, infra):
Infrastructure(infra).deploy(
service.name, service.config, service.segment)
if __name__ == '__main__':
ntp1 = NetworkServiceUser('NTP', 'ntp_config.txt')
ntp1.deploy_service('Production')
print('-' * 30)
ntp2 = NetworkServiceMgmt('NTP', 'ntp_config.txt')
ntp2.deploy_service('Test')

Output:

Deploy service: NTP
On infrastructure: Production
In segment: 10.0.1.0/24
From config: ntp_config.txt
------------------------------
Deploy service: NTP
On infrastructure: Test
In segment: 10.0.2.0/24
From config: ntp_config.txt

ขอทวนอีกรอบนะครับ ตอนนี้ code ของเรานั้น compile กับ 2 ตัวนี้ละ ได้ SO ละ

  1. Single Responsibility Principle = แต่ละ class ทำหน้าที่เดียว
  2. Open-closed principle = เพิ่ม feature ใหม่โดยไม่ต้องแก้ส่วนเดิมที่ใช้อยู่

ทีนี้ยังเหลือ LID อีก 3 ตัว ไว้ถ้ามีเวลาจะมาเขียนต่อครับ…

บทความนี้เสร็จไป 60% แล้ว ถ้ามีเวลาจะมาต่อนะ

— — — — — — — — — — — — — — —
สารบัญเนื้อหาทั้งหมด (My Contents)
— — — — — — — — — — — — — — —

--

--

Nopnithi Khaokaew (Game)
Nopnithi Khaokaew (Game)

Written by Nopnithi Khaokaew (Game)

Cloud Solutions Architect & Hobbyist Developer | 6x AWS Certified, CKA, CKAD, 2x HashiCorp Certified (Terraform, Vault), etc.