Ich habe beschlossen, einen Prozess zu erstellen, der CSV von außen aufnimmt und in eine in meinem System verwendete Datei konvertiert.
Es war nicht so umfangreich, also wäre es schön gewesen, ein prozedurales Programm zu schreiben, aber ich hatte etwas Zeit übrig, also habe ich die Ebenen ernsthaft aufgeteilt.
Zu diesem Zeitpunkt möchte ich zusammen mit den Anforderungen des Musters schreiben, wie ich es gemacht habe.
Dieses Mal, einschließlich des Studiums, werde ich es mit Python 3.7 und Lambda schaffen.
https://github.com/jnuank/io_logic_isolation
Bitte beziehen Sie sich auf [Final Separation Image](# Final Separation Image).
Transaktionsdaten werden aus der von einem Filialisten verwendeten Registrierkasse entnommen, und die an jeder Registrierkasse jedes Geschäfts generierten Transaktionen werden in der CSV von Verkäufen aus anderen Systemen erfasst und in Daten konvertiert, die im eigenen System verwendet werden können. Zu diesem Zeitpunkt wird die CSV-Lesung dieses Mal nicht in das Leerzeichen-Trennzeichen konvertiert, sondern einige müssen in den Wert konvertiert werden, den das System hat.
Daten für jedes Geschäft und jede Registrierkasse für einen bestimmten Tag werden in einer Datei gesammelt.
Daten aus der Registrierkasse gesaugt
#Lesen von Änderungen in Abhängigkeit vom dritten Datensatztyp
# 01: 1:Speichern Sie den Code 2:Registrierkasse Nummer 3:Typschlüssel(01:Transaktionsheader) 4:Transaktionsnummer 5:YYYYMMDDHHMMSS
# 02: 1:Speichern Sie den Code 2:Registrierkasse Nummer 3:Typschlüssel(02:Transaktionsdetails) 4:Transaktionsnummer 5:Produktname 6:Stückpreis 7:Menge
"1","1","01","0000001", "20200816100000"
"1","1","02","0000001","Produkt A.","1000", "2"
"1","1","02","0000001","Produkt B.","500", "4"
"1","1","02","0000001","Produkt C.","100", "10"
"1","1","01","0000002", "20200816113010"
"1","1","02","0000002","Produkt D.","10000", "1"
"1","1","02","0000002","Produkt E.","2000", "1"
"1","2","01","0000001", "20200816102049"
"1","2","02","0000001","Produkt A.","1000", "3"
"1","2","02","0000001","Produkt D.","10000", "2"
"1","2","02","0000001","Produkt F.","500", "5"
"1","2","02","0000001","Produkt G.","4400", "2"
"2","1","01","0000001", "20200816152009"
"2","1","02","0000001","Produkt F.","500", "1"
"2","1","02","0000001","Produkt G.","4400", "1"
Monat speichern
# 1:Speichern Sie den Code 2:Verkaufsdatum(YYYYMM) 3:Verkaufszahlen
001 202008 500000
002 202008 300000
003 202008 900000
Bis zum Geschäftstag
# 1:Speichern Sie den Code 2:Verkaufsdatum(YYYYMMDD) 3:Verkaufszahlen
001 20200816 51300
002 20200816 4900
Speichern Sie tägliche Details
# 1:Speichern Sie den Code 2:Transaktionsnummer 3:Registrierkasse Nummer 4:Verkaufszeit(YYYYMMDDHHMMSS) 5:Verkaufszahlen
001 0000001 001 20200816100000 5000
001 0000002 001 20200816113010 12000
001 0000001 901 20200816102049 34300
002 0000001 001 20200816152009 4900
――Was möchten Sie aus den heruntergeladenen Daten wissen?
Ich werde so ein Lambda machen.
Da es sich nur um eine Konvertierung handelt, fällt es mir leicht, in der Handler-Methode eine solide Logik zu schreiben, aber ich neige dazu, dies zu tun. In einem solchen Fall zwingen Sie die folgenden Ereignisse, die Logik zu ändern.
--Wenn sich die Struktur der CSV-Daten ändert --Wenn sich die Struktur der durch Leerzeichen getrennten Datei ändert ――Es handelt sich in erster Linie nicht um CSV, sondern nicht durch Leerzeichen
Es scheint, dass ** 2 Typen ** Änderungsanforderungen aufgrund von Änderungen in der Eingabe- / Ausgabedatenstruktur und Regeln wie Berechnungen möglich sind.
Insbesondere beim Empfang von Daten von einem anderen System wie dieser Zeit kann sich die Definition der Datenelemente von der anderen Partei verzögern oder es kann schwierig sein, Beispieldaten zu erhalten. Wenn die Entwicklung dort nicht fortgesetzt würde oder wenn sie mit den Spezifikationen durchgeführt würde, die ich mündlich gehört hatte, würde man sagen: "Eigentlich waren das alte Informationen, also ist es jetzt anders."
Persönlich war ich nicht an Python gewöhnt, aber ich fing an, Tests zu schreiben.
Trennen Sie zu diesem Zeitpunkt den Lambda-Handler und die Logik.
Der Lambda-Handler empfängt eine ** Anfrage (Ereignis, Kontext) und gibt schließlich eine Antwort (HTTP-Status) ** zurück. Dies liegt in der Verantwortung der Lambda-Handler. Halten Sie daher die Konvertierungslogik von den Handlern fern. Extrahieren Sie die erforderlichen Parameter, übertragen Sie sie in die Logikklasse, empfangen Sie die Ergebnisse und nehmen Sie sie (falls erforderlich) in die Antwort auf.
handler.py
def import_handler(event, context):
try:
#Extrahieren Sie die erforderlichen Informationen aus dem Ereignis
body_str = event['body']
body = json.loads(body_str)
key = body['key']
bucket = os.environ['BUCKET_NAME']
#CSV importieren → Durch Leerzeichen getrennt speichern
trans_app = CsvToSpaceSeparateApplication(bucket, key)
trans_app.csv_to_space_separate()
dict_value = {'message': 'hochgeladen', }
json_str = json.dumps(dict_value)
return {
'statusCode': 200,
'body': json_str
}
Eigentlich war ich nicht an Python gewöhnt, also konnte ich einen Test schreiben und ausprobieren, wie man CSV liest und wie man in Raumbegrenzer konvertiert, also war es gut, hier zuerst zu trennen.
https://github.com/jnuank/io_logic_isolation/commit/539b7d8bcdf8ca1b253b6185ab88f0b98806f8b4
Ich möchte die gelesene CSV nicht in Leerzeichen umwandeln, aber ich denke, dass einige Werte möglicherweise in die Werte des Systems konvertiert werden möchten.
Der Geschäftscode und die Registrierkassennummer des externen Systems sind die installierten Seriennummern. Innerhalb des Systems gibt es Unterschiede im Nummerierungssystem, beispielsweise die Tatsache, dass jede Ziffer eine Bedeutung hat.
Daher hätte ich gerne eine Tabelle zum Konvertieren, aber da der Datenspeicher noch nicht festgelegt wurde, denke ich, dass es Zeiten gibt, in denen ich eine temporäre Tabelle zum Testen verwende. Wenn Sie die Implementierungsdetails einer bestimmten Datenquelle (z. B. Herstellen einer Verbindung) in die Konvertierungslogik schreiben, müssen Sie diese ändern, wenn sich die Datenquelle ändert.
Um dies zu vermeiden und die Entscheidung über die Implementierungsdetails zu verzögern, bereiten Sie eine abstrakte Klasse vor, die erwartet, dass sie den Wert zurückgibt, den das System hat, nachdem sie den von CSV erhaltenen Wert vorerst übergeben hat.
Abstrakte Klasse zum Konvertieren von CSV-Werten in Systemwerte
from abc import ABCMeta, abstractmethod
class CodeRepositoryBase(object, metaclass=ABCMeta):
"""
Abstrakte Klasse zum Abrufen von Code aus dem Datenspeicher
"""
@abstractmethod
def get_shop_code(self, external_system_shop_code: str) -> str:
"""
Holen Sie sich den Geschäftscode
:param external_system_shop_code:Speichern Sie den von einem externen System nummerierten Code
:return:Geschäftscode
"""
raise NotImplementedError()
@abstractmethod
def get_cash_register_code(self, external_system_shop_code: str, external_system_cash_register_code: str) -> str:
"""
Holen Sie sich eine Registrierkassennummer
:param external_system_shop_code:Speichern Sie den von einem externen System nummerierten Code
:param external_system_cash_register_code:Registriernummern, die von einem externen System vergeben wurden
:return:Registriernummer
"""
raise NotImplementedError()
Testen Sie das Repository mit Daten in dikt
from source.domain.repository.code_repository_base import CodeRepositoryBase
class InMemoryCodeRepository(CodeRepositoryBase):
"""
In-Memory-Repository-Implementierung
"""
def __init__(self):
# key:Externer Systemspeichercodewert:Geschäftscode
self.__shop_code_table = {
'1': '001',
'2': '002',
'3': '003'
}
# key:(Externer Systemspeichercode,Nummer der externen Systemkasse) value:Registriernummer
#Die erste Ziffer der Registrierungsnummer ist "0".:Permanente Registrierkasse "9":Event-Registrierkasse
self.__cash_register_code_table = {
('1', '1'): '001',
('1', '2'): '901',
('2', '1'): '001',
}
def get_shop_code(self, external_system_shop_code: str) -> str:
"""
Holen Sie sich den Geschäftscode
:param external_system_shop_code:Speichern Sie den von einem externen System nummerierten Code
:return:Geschäftscode
"""
result = self.__shop_code_table.get(external_system_shop_code)
if result is None:
raise ValueError(f'Der dem angegebenen Schlüssel entsprechende Geschäftscode existiert nicht. Schlüssel:{external_system_shop_code}')
return result
def get_cash_register_code(self, external_system_shop_code: str, external_system_cash_register_code:str) -> str:
"""
Holen Sie sich eine Registrierkassennummer
:param external_system_shop_code:Speichern Sie den von einem externen System nummerierten Code
:param external_system_cash_register_code:Registriernummern, die von einem externen System vergeben wurden
:return:Registriernummer
"""
result = self.__cash_register_code_table.get((external_system_shop_code, external_system_cash_register_code))
if result is None:
raise ValueError(f'Die dem angegebenen Schlüssel entsprechende Registrierungsnummer existiert nicht. Schlüssel:{external_system_cash_register_code}')
return result
Testcode
from pytest import raises
from tests.In_memory_code_repository import InMemoryCodeRepository
class TestInMemoryCodeRepository:
def test_Der Geschäftscode 001 wird zurückgegeben(self):
result = InMemoryCodeRepository().get_shop_code('1')
assert result == '001'
――Da die Datenquelle festgelegt wurde, dauerte es länger als gewöhnlich, da erneut über die Details der Implementierung nachgedacht werden musste.
https://github.com/jnuank/io_logic_isolation/commit/1c54107aafb72d3faee57b3ef85a5510f794deae
Eine Trennung war möglich, bis der Wert von CSV in den Wert des eigenen Systems umgewandelt wurde.
Basierend auf den folgenden CSV-Daten werden wir sie nun in einen durch Leerzeichen getrennten Wert konvertieren.
[Repost] Von der Registrierkasse gesammelte Daten
#Lesen von Änderungen in Abhängigkeit vom dritten Datensatztyp
# 01: 1:Speichern Sie den Code 2:Registrierkasse Nummer 3:Typschlüssel(01:Transaktionsheader) 4:Transaktionsnummer 5:YYYYMMDDHHMMSS
# 02: 1:Speichern Sie den Code 2:Registrierkasse Nummer 3:Typschlüssel(02:Transaktionsdetails) 4:Transaktionsnummer 5:Produktname 6:Stückpreis 7:Menge
"1","1","01","0000001","20200816100000"
"1","1","02","0000001","Produkt A.","1000","2"
"1","1","02","0000001","Produkt B.","500","4"
"1","1","02","0000001","Produkt C.","100","10"
"1","1","01","0000002","20200816113010"
"1","1","02","0000002","Produkt D.","10000","1"
"1","1","02","0000002","Produkt E.","2000","1"
"1","2","01","0000001","20200816102049"
"1","2","02","0000001","Produkt A.","1000","3"
"1","2","02","0000001","Produkt D.","10000","2"
"1","2","02","0000001","Produkt F.","500","5"
"1","2","02","0000001","Produkt G.","4400","2"
"2","1","01","0000001","20200816152009"
"2","1","02","0000001","Produkt F.","500","1"
"2","1","02","0000001","Produkt G.","4400","1"
Schreiben Sie die Zuordnung basierend auf dem Dokument des Tabellendefinitionselements. Ich habe es sehr grob geschrieben und es sieht so aus.
app.py
#Gibt eine Liste zurück, die gemäß der Elementdefinition basierend auf der übergebenen CSV zugeordnet ist
#Ändern Sie die Liste in ein Leerzeichen, das auf der Anruferseite begrenzt ist
@dataclass
class CsvToShopSales:
code_respository: CodeRepositoryBase
def csv_to_sales_by_shop(self, csv_list) -> List[List[str]]:
names_list = list(range(10))
df = pd.read_csv(csv_list, names=names_list, dtype='object').fillna('_')
SHOP_COLUMN = 0
#Nach Geschäftscode gruppieren
shop_group_list = df.groupby(SHOP_COLUMN)
results = []
for group_rows in shop_group_list:
shop_code = self.code_respository.get_shop_code(group_rows[0])
year_month = [record[4] for record in group_rows[1].values.tolist() if record[2] == '01'][0][:6]
amount_list = [int(record[5]) * int(record[6]) for record in group_rows[1].values.tolist() if record[2] == '02']
sales_amount = sum(amount_list)
results.append([shop_code, year_month, str(sales_amount)])
return results
Unabhängig davon, welche Seite der Datenstruktur sich ändert, die Daten vor der Konvertierung oder die Daten nach der Konvertierung, halte ich es nicht für eine gute Idee, denselben Code zu ändern. Ich möchte nicht mehr als einen Grund haben, für eine Klasse zu wechseln.
Lassen Sie uns diese von app.py trennen. Unten ist ein Bild des Inhalts von app.py getrennt.
Repository zum Konvertieren von CSV in Modell
from abc import ABCMeta, abstractmethod
from typing import List
from source.domain.models.csv_models.csv_cash_transaction_header import CsvCashTransactionHeader
class CsvCashTransactionRepositoryBase(object, metaclass=ABCMeta):
"""
Repository abstrakte Klasse zum Empfangen von CSV-Registrierkassen-Transaktionsdaten
"""
@abstractmethod
def load(self) -> List[CsvCashTransactionHeader]:
"""
Holen Sie sich das Transaktionsdatenmodell für Registrierkassen
:return:Datenmodell für Registrierkassen-Transaktionen
"""
raise NotImplementedError()
@abstractmethod
def save(self, data: CsvCashTransactionHeader) -> None:
"""
Speichern Sie das Transaktionsdatenmodell der Registrierkasse
:param data:Datenmodell für Registrierkassen-Transaktionen
"""
raise NotImplementedError('Kann noch nicht speichern')
CSV-Modell
from __future__ import annotations
from dataclasses import dataclass, field
from typing import List
from source.domain.models.csv_models.csv_cash_transaction_detail import CsvCashTransactionDetail
@dataclass(frozen=True, order=True)
class CsvCashTransactionHeader:
"""
CSV-Modell der Registrierkassen-Transaktionsdaten
"""
#Geschäftscode
shop_code: str = field(compare=True)
#Registriernummer
cash_register_code: str = field(compare=True)
#Übergangsnummer
transaction_code: str = field(compare=True)
#Handelszeit
transaction_datetime: str = field(compare=True)
#Transaktionsdetails
transaction_details: List[CsvCashTransactionDetail]
from dataclasses import dataclass
@dataclass(frozen=True)
class CsvCashTransactionDetail:
"""
Details zu den Transaktionsdaten der Registrierkasse CSV-Modell
"""
#Produktname
item_name: str
#Stückpreis
unit_price: int
#Menge
quantity: int
Als Model sieht es so aus.
Erstellen Sie ein Domänenmodell wie unter [Bild des Datenmodells nach dem Import] beschrieben (# Bild des Datenmodells nach dem Import).
from dataclasses import dataclass, field
from functools import reduce
from operator import add
from typing import List
from source.domain.models.salses.daily_sales import DailySales
@dataclass(frozen=True)
class ShopMonthlySales:
shop_code: str
year_month: str
daily_sales_list: List[DailySales] = field(default_factory=list, compare=False)
def amount(self) -> int:
return reduce(add, map(lambda data: data.amount(), self.daily_sales_list))
from dataclasses import dataclass, field
from datetime import datetime
from functools import reduce
from operator import add
from typing import List
from source.domain.models.salses.daily_sales_detail import DailySalesDetail
@dataclass(frozen=True)
class DailySales:
sales_date: datetime.date
details: List[DailySalesDetail] = field(default_factory=list, compare=False)
def amount(self) -> int:
return reduce(add, map(lambda data: data.amount, self.details))
import datetime
from dataclasses import dataclass
@dataclass(frozen=True)
class DailySalesDetail:
transaction_code: str
transaction_datetime: datetime.datetime
cash_number: str
amount: int
Erstellen Sie eine Regelklasse, die ein CSV-Modell in ein Vertriebsdomänenmodell umwandelt.
@dataclass(frozen=True)
class TransferRules(object):
"""
Konvertierungsregelklasse
"""
repository: CodeRepositoryBase
def to_shop_sales(self, sources: List[CsvCashTransactionHeader]) -> List[ShopMonthlySales]:
results: List[ShopMonthlySales] = []
sources.sort(key=lambda x: x.shop_code)
#Nach Geschäft gruppieren und in Modell konvertieren
for key, g in groupby(sources, key=lambda x: x.shop_code):
shop_code = self.repository.get_shop_code(key)
details: List[DailySalesDetail] = []
dt = ''
day = ''
year_month = ''
for member in g:
dt = datetime.strptime(member.transaction_datetime, '%Y%m%d%H%M%S')
day = date(dt.year, dt.month, dt.day)
year_month = member.transaction_datetime[:6]
cash_register_code = self.repository.get_cash_register_code(member.shop_code, member.cash_register_code)
amount = sum([s.unit_price * s.quantity for s in member.transaction_details])
detail = DailySalesDetail(member.transaction_code,
dt,
cash_register_code,
amount)
details.append(detail)
daily = DailySales(day, details)
shop_sales = ShopMonthlySales(shop_code, year_month, [daily])
results.append(shop_sales)
return results
Erstellen Sie eine Klasse, die das Domänenmodell im Datenspeicher getrennt durch Leerzeichen speichert.
Dieses Mal erstellen wir eine Klasse, die in S3 gespeichert werden soll.
class S3ShopSalesRepository(ShopSalesRepositoryBase):
"""
Implementierung des In-Memory-Store-Verkaufsrepositorys
"""
__bucket_name: str
def __init__(self, bucket_name):
self.__bucket_name = bucket_name
def save(self, sources: List[ShopMonthlySales]) -> None:
self.shop_monthly_sales = []
self.daily_sales = []
self.daily_details = []
for source in sources:
self.shop_monthly_sales.append(
[source.shop_code, source.year_month, str(source.amount())]
)
for daily in source.daily_sales_list:
self.daily_sales.append([
source.shop_code,
daily.sales_date.strftime('%Y%m%d'),
str(daily.amount()),
])
for detail in daily.details:
self.daily_details.append(
[source.shop_code,
detail.transaction_code,
detail.cash_number,
detail.transaction_datetime.strftime('%Y%m%d%H%M%S'),
str(detail.amount)]
)
self.shop_monthly_sales = self.__comma2dlist_to_space2dlist(self.shop_monthly_sales)
self.daily_sales = self.__comma2dlist_to_space2dlist(self.daily_sales)
self.daily_details = self.__comma2dlist_to_space2dlist(self.daily_details)
try:
self.__s3_upload(self.shop_monthly_sales, self.__bucket_name, 'Ladenverkäufe.txt')
self.__s3_upload(self.daily_details, self.__bucket_name, 'Bis zum Geschäftstag.txt')
self.__s3_upload(self.daily_details, self.__bucket_name, 'Speichern Sie tägliche Details.txt')
except Exception as error:
raise error
Der Prozess des Speicherns im Datenspeicher und des Konvertierens in Leerzeichen wird in dieser Klasse kombiniert. Der Grund dafür ist, dass es diesmal in einen durch Leerzeichen getrennten Speicher konvertiert wird. Bei der Konvertierung in einen anderen Datenspeicher kann es jedoch in ein anderes Format konvertiert werden, also in die Implementierungsklasse von "ShopSalesRepositoryBase" Ich überlasse es dir.
handler.py
def import_handler(event, context):
try:
#Extrahieren Sie die erforderlichen Informationen aus dem Ereignis
body_str = event['body']
body = json.loads(body_str)
key = body['key']
bucket_name = os.environ['BUCKET_NAME']
code_repository = InMemoryCodeRepository()
csv_repository = S3CsvCashTransactionRepository(key, bucket_name)
#Vorausgesetzt, dieser Eimer wurde bereits entschieden
shop_sales_repository = S3ShopSalesRepository('xxxxx-bucket')
#CSV importieren → Durch Leerzeichen getrennt speichern
trans_app = CsvToSpaceSeparateApplication(code_repository, csv_repository, shop_sales_repository)
trans_app.csv_to_space_separate()
#Antwortmontage
dict_value = {'message': 'hochgeladen', }
json_str = json.dumps(dict_value)
return {
'statusCode': 200,
'body': json_str
}
except ValueError as error:
logger.exception(f'{error}')
dict_value = {'message': f'{error}', }
json_str = json.dumps(dict_value)
return {
'statusCode': 500,
'body': json_str
}
except Exception as error:
logger.exception(f'{error}')
dict_value = {'message': f'Ein Verarbeitungsfehler ist aufgetreten. Bitte versuchen Sie es nach einer Weile erneut', }
json_str = json.dumps(dict_value)
return {
'statusCode': 500,
'body': json_str
}
application
@dataclass
class CsvToSpaceSeparateApplication(object):
"""
CSV → Durch Leerzeichen getrennter Konvertierungsprozess
"""
code_repository: CodeRepositoryBase
csv_repository: CsvCashTransactionRepositoryBase
shop_sales_repository: ShopSalesRepositoryBase
def csv_to_space_separate(self) -> None:
"""
CSV → durch Leerzeichen getrennte Konvertierung
"""
#In CSV-Modell konvertieren
csv_models = self.csv_repository.load()
#In Domänenmodell konvertieren
shop_monthly_sales = TransferRules(self.code_repository).to_shop_sales(csv_models)
#In Leerzeichen konvertieren und speichern
self.shop_sales_repository.save(shop_monthly_sales)
Das vom Handler aufgerufene Bild in der CsvToSpaceSeparateApplication sieht folgendermaßen aus. Jede Prozedur "Ausgabe-> Konvertierung-> Ausgabe" wird durch die Methode in der Anwendungsschicht ausgedrückt.
Die Absicht jedes Prozesses wird auch durch Gruppieren in eine Klasse ausgedrückt.
https://github.com/jnuank/io_logic_isolation/commit/b4e8885b2f269a608d0cfe3bfb414d4135277022
Ich habe versucht, eine ähnliche Konfiguration vor Ort zu üben.
――Da die Eingabe / Ausgabe und Konvertierung getrennt wurden und kurz vor der Veröffentlichung stehen, wurde ich darüber informiert, dass sich die CSV-Konfiguration von anderen Systemen ändern wird. --Erstellen Sie eine neue Klasse, die die abstrakte Klasse des Repositorys auf der CSV-Seite erbt, und DI sie von handler.py. ―― Eigentlich war die Regel des Domänenmodells nur die Berechnung des Gesamtbetrags, aber es wurde darüber gesprochen, ob die Berechnungsregel in der Mitte geändert werden könnte. ――Es kam nicht als Ergebnis, aber ich konnte zu diesem Zeitpunkt beurteilen, dass es nicht schwierig war, etwas hinzuzufügen, da ich nur die Logik des Berechnungsurteils getrennt habe.
Es war ein kleiner Prozess, aber dieses Mal habe ich versucht, die Eingabe- / Ausgabe- und Berechnungs- / Beurteilungsregeln zu trennen, und ich hatte das Gefühl, dass dies für den Aufbau eines großen Systems in der Zukunft nützlich sein würde. Es scheint, dass es Gespräche über Kosteneffizienz geben wird, aber wenn ich etwas Zeit habe, würde ich gerne versuchen, mich dessen regelmäßig bewusst zu werden. (Natürlich ist dies bei dem Code, den Sie wegwerfen möchten, nicht der Fall, aber meistens nicht ...)
Recommended Posts