PostgreSQL сакталган функциялардын деңгээлинде бизнес логикасын ишке ашыруу боюнча изилдөө

Бул эскизди жазууга макала түрткү болду "Карантин учурунда жүк 5 эсеге көбөйдү, бирок биз даяр болчубуз." Lingualeo кантип 23 миллион колдонуучу менен PostgreSQLге өткөн. Мен дагы 4 жыл мурун жарыяланган макала кызыктуу болду - MySQLде бизнес логикасын ишке ашыруу.

Ошол эле ой кызык көрүндү - "маалымат базасында бизнес логикасын ишке ашыруу".

PostgreSQL сакталган функциялардын деңгээлинде бизнес логикасын ишке ашыруу боюнча изилдөө

Мен эле оюма келген жокмун.

Ошондой эле, келечекте, мен, биринчи кезекте, өзүм үчүн, ишке ашыруу учурунда пайда болгон кызыктуу окуяларды сактап калгым келди. Айрыкча, салыштырмалуу жакында эле архитектураны өзгөртүү жана бизнес-логиканы бэкенддик деңгээлге өткөрүү боюнча стратегиялык чечим кабыл алынганын эске алганда. Ошентип, иштелип чыккандын бардыгы жакында эч кимге пайдасыз жана эч кимди кызыктырбай калат.

сүрөттөлгөн ыкмалар кандайдыр бир ачылыш же өзгөчө эмес. технология, баары классикалык жана бир нече жолу ишке ашырылган (мисалы, мен 20 жыл мурун Oracleда ушундай ыкманы колдонгом) Мен жөн гана баарын бир жерге чогултууну чечтим. Эгер кимдир бирөө үчүн пайдалуу болсо. Практика көрсөткөндөй, бир эле идея ар кандай адамдарга өз алдынча келет. Ал эми аны сувенир катары өзүңүзгө сактоо пайдалуу.

Албетте, бул дүйнөдө эч нерсе идеалдуу эмес, каталар жана каталар, тилекке каршы, мүмкүн. Сын-пикирлер жана сын-пикирлер катуу кабыл алынат жана күтүлөт жана дагы бир майда-чүйдөсүнө чейин - конкреттүү ишке ашыруу деталдары калтырылган. Ошентсе да, баары дагы эле чыныгы жумушчу долбоордо колдонулат. Демек, макала жөн гана эскиз жана жалпы түшүнүктүн сүрөттөлүшү, башка эч нерсе эмес. Мен жалпы сүрөттү түшүнүү үчүн жетиштүү маалымат бар деп үмүттөнөм.

Жалпы идея "бөлүп ал жана жең, жашын жана ээлик кыл"

Идея классикалык - таблицалар үчүн өзүнчө схема, сакталган функциялар үчүн өзүнчө схема.
Кардар маалыматтарга түздөн-түз кире албайт. Кардар сакталган функцияны чакырып, кабыл алынган жоопту иштете алат.

ролу

CREATE ROLE store;

CREATE ROLE sys_functions;

CREATE ROLE loc_audit_functions;

CREATE ROLE service_functions;

CREATE ROLE business_functions;

схемалары

Стол сактоо схемасы

Предметтик объекттерди ишке ашыруучу максаттуу таблицалар.

CREATE SCHEMA store AUTHORIZATION store ;

Системанын иштешинин диаграммасы

Системалык функциялар, атап айтканда, таблицадагы өзгөрүүлөрдү каттоо үчүн.

CREATE SCHEMA sys_functions AUTHORIZATION sys_functions ;

Жергиликтүү аудит схемасы

Сакталган функциялардын аткарылышынын жергиликтүү аудитин жана максаттуу таблицаларга өзгөртүүлөрдү киргизүү үчүн функциялар жана таблицалар.

CREATE SCHEMA loc_audit_functions AUTHORIZATION loc_audit_functions;

Кызмат функциясынын диаграммасы

Кызмат жана DML функциялары үчүн функциялар.

CREATE SCHEMA service_functions AUTHORIZATION service_functions;

Бизнес-функция диаграммасы

Кардар чакырган акыркы бизнес функциялары үчүн функциялар.

CREATE SCHEMA business_functions AUTHORIZATION business_functions;

Кирүү укугу

Рол - DBAs бардык схемаларга толук мүмкүнчүлүгү бар (МБ Ээсинин ролунан бөлүнгөн).

CREATE ROLE dba_role;
GRANT store TO dba_role;
GRANT sys_functions TO dba_role;
GRANT loc_audit_functions TO dba_role;
GRANT service_functions TO dba_role;
GRANT business_functions TO dba_role;

Рол - КОЛДОНУУЧУ артыкчылыкка ээ АТКАРУУ диаграммада бизнес_функциялары.

CREATE ROLE user_role;

Схемалардын ортосундагы артыкчылыктар

ГРАНТ
Анткени бардык функциялар атрибут менен түзүлгөн КООПСУЗДУКТУ АНЫКТООЧУ керектүү көрсөтмөлөр БАРДЫК ФУНКЦИЯЛАРДА АТКАРЫЛУУНУ ЖОГОРКУ АЛУУ… ачыктан;

REVOKE EXECUTE ON ALL FUNCTION IN SCHEMA sys_functions FROM public ; 
REVOKE EXECUTE ON ALL FUNCTION IN SCHEMA  loc_audit_functions  FROM public ; 
REVOKE EXECUTE ON ALL FUNCTION IN SCHEMA  service_functions FROM public ; 
REVOKE EXECUTE ON ALL FUNCTION IN SCHEMA  business_functions FROM public ; 

GRANT USAGE ON SCHEMA sys_functions TO dba_role ; 
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA sys_functions TO dba_role ;
GRANT USAGE ON SCHEMA loc_audit_functions  TO dba_role ; 
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA loc_audit_functions  TO dba_role ;
GRANT USAGE ON SCHEMA service_functions TO dba_role ; 
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA service_functions TO dba_role ;
GRANT USAGE ON SCHEMA business_functions TO dba_role ; 
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA business_functions TO dba_role ;
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA business_functions TO user_role ;

GRANT ALL PRIVILEGES ON SCHEMA store TO GROUP business_functions ;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA store TO business_functions ;
GRANT USAGE ON ALL SEQUENCES IN SCHEMA store TO business_functions ;

Ошентип, маалымат базасынын схемасы даяр. Сиз маалыматтарды толтуруп баштаса болот.

Максаттуу таблицалар

Таблицаларды түзүү анча маанилүү эмес. Эч кандай өзгөчө функциялар, аны колдонбоо чечими кабыл алынганын эске албаганда СЕРИЯ жана ырааттуулукту ачык генерациялайт. Plus, албетте, көрсөтмөлөрдү максималдуу пайдалануу

COMMENT ON ...

үчүн комментарийлер всех объектилер, кошпостон.

Жергиликтүү аудит

Сакталган функциялардын аткарылышын жана максаттуу таблицаларга өзгөртүүлөрдү киргизүү үчүн локалдык аудит таблицасы колдонулат, ал башка нерселер менен катар кардар байланышынын деталдарын, чакырылган модулдун этикеткасын жана киргизүүнүн чыныгы маанилерин жана чыгаруу параметрлери JSON түрүндө.

Системалык функциялар

Максаттуу таблицалардагы өзгөртүүлөрдү киргизүү үчүн иштелип чыккан. Алар триггер функциялары.

Шаблон - системанын функциясы

---------------------------------------------------------
-- INSERT
CREATE OR REPLACE FUNCTION sys_functions.table_insert_log ()
RETURNS TRIGGER AS $$
BEGIN
  PERFORM loc_audit_functions.make_log( ' '||'table' , 'insert' , json_build_object('id', NEW.id)  );
  RETURN NULL ;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;

CREATE TRIGGER table_after_insert AFTER INSERT ON storage.table FOR EACH ROW EXECUTE PROCEDURE sys_functions.table_insert_log();

---------------------------------------------------------
-- UPDATE
CREATE OR REPLACE FUNCTION sys_functions.table_update_log ()
RETURNS TRIGGER AS $$
BEGIN
  IF OLD.column != NEW.column
  THEN
    PERFORM loc_audit_functions.make_log( ' '||'table' , 'update' , json_build_object('OLD.column', OLD.column , 'NEW.column' , NEW.column )  );
  END IF ;
  RETURN NULL ;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;

CREATE TRIGGER table_after_update AFTER UPDATE ON storage.table FOR EACH ROW EXECUTE PROCEDURE sys_functions.table_update_log ();

---------------------------------------------------------
-- DELETE
CREATE OR REPLACE FUNCTION sys_functions.table_delete_log ()
RETURNS TRIGGER AS $$
BEGIN
  PERFORM loc_audit_functions.make_log( ' '||'table' , 'delete' , json_build_object('id', OLD.id )  );
  RETURN NULL ;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;

CREATE TRIGGER table_after_delete AFTER DELETE ON storage.table FOR EACH ROW EXECUTE PROCEDURE sys_functions.table_delete_log ();

Тейлөө функциялары

Максаттуу таблицаларда тейлөө жана DML операцияларын ишке ашыруу үчүн иштелип чыккан.

Үлгү - тейлөө функциясы

--INSERT
--RETURN id OF NEW ROW
CREATE OR REPLACE FUNCTION service_functions.table_insert ( new_column store.table.column%TYPE )
RETURNS integer AS $$
DECLARE
  new_id integer ;
BEGIN
  -- Generate new id
  new_id = nextval('store.table.seq');

  -- Insert into table
  INSERT INTO store.table
  ( 
    id ,
    column
   )
  VALUES
  (
   new_id ,
   new_column
   );

RETURN new_id ;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;

--DELETE
--RETURN ROW NUMBERS DELETED
CREATE OR REPLACE FUNCTION service_functions.table_delete ( current_id integer ) 
RETURNS integer AS $$
DECLARE
  rows_count integer  ;    
BEGIN
  DELETE FROM store.table WHERE id = current_id; 

  GET DIAGNOSTICS rows_count = ROW_COUNT;                                                                           

  RETURN rows_count ;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;
 
-- UPDATE DETAILS
-- RETURN ROW NUMBERS UPDATED
CREATE OR REPLACE FUNCTION service_functions.table_update_column 
(
  current_id integer 
  ,new_column store.table.column%TYPE
) 
RETURNS integer AS $$
DECLARE
  rows_count integer  ; 
BEGIN
  UPDATE  store.table
  SET
    column = new_column
  WHERE id = current_id;

  GET DIAGNOSTICS rows_count = ROW_COUNT;                                                                           

  RETURN rows_count ;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;

Бизнес функциялары

Кардар чакырган акыркы бизнес функциялары үчүн иштелип чыккан. Алар ар дайым кайтып келет - JSON. Аткаруу каталарын кармоо жана журналга коюу үчүн блокту колдонуңуз ЧЫГАРУУ.

Шаблон - бизнес функциясы

CREATE OR REPLACE FUNCTION business_functions.business_function_template(
--Input parameters        
 )
RETURNS JSON AS $$
DECLARE
  ------------------------
  --for exception catching
  error_message text ;
  error_json json ;
  result json ;
  ------------------------ 
BEGIN
--LOGGING
  PERFORM loc_audit_functions.make_log
  (
    'business_function_template',
    'STARTED',
    json_build_object
    (
	--IN Parameters
    ) 
   );

  PERFORM business_functions.notice('business_function_template');            

  --START BUSINESS PART
  --END BUSINESS PART

  -- SUCCESFULLY RESULT
  PERFORM business_functions.notice('result');
  PERFORM business_functions.notice(result);

  PERFORM loc_audit_functions.make_log
  (
    'business_function_template',
    'FINISHED', 
    json_build_object( 'result',result )
  );

  RETURN result ;
----------------------------------------------------------------------------------------------------------
-- EXCEPTION CATCHING
EXCEPTION                        
  WHEN OTHERS THEN    
    PERFORM loc_audit_functions.make_log
    (
      'business_function_template',
      'STARTED',
      json_build_object
      (
	--IN Parameters	
      ) , TRUE );

     PERFORM loc_audit_functions.make_log
     (
       'business_function_template',
       ' ERROR',
       json_build_object('SQLSTATE',SQLSTATE ), TRUE 
     );

     PERFORM loc_audit_functions.make_log
     (
       'business_function_template',
       ' ERROR',
       json_build_object('SQLERRM',SQLERRM  ), TRUE 
      );

     GET STACKED DIAGNOSTICS error_message = RETURNED_SQLSTATE ;
     PERFORM loc_audit_functions.make_log
     (
      'business_function_template',
      ' ERROR-RETURNED_SQLSTATE',json_build_object('RETURNED_SQLSTATE',error_message  ), TRUE );

     GET STACKED DIAGNOSTICS error_message = COLUMN_NAME ;
     PERFORM loc_audit_functions.make_log
     (
       'business_function_template',
       ' ERROR-COLUMN_NAME',
       json_build_object('COLUMN_NAME',error_message  ), TRUE );

     GET STACKED DIAGNOSTICS error_message = CONSTRAINT_NAME ;
     PERFORM loc_audit_functions.make_log
     (
      'business_function_template',
      ' ERROR-CONSTRAINT_NAME',
      json_build_object('CONSTRAINT_NAME',error_message  ), TRUE );

     GET STACKED DIAGNOSTICS error_message = PG_DATATYPE_NAME ;
     PERFORM loc_audit_functions.make_log
     (
       'business_function_template',
       ' ERROR-PG_DATATYPE_NAME',
       json_build_object('PG_DATATYPE_NAME',error_message  ), TRUE );

     GET STACKED DIAGNOSTICS error_message = MESSAGE_TEXT ;
     PERFORM loc_audit_functions.make_log
     (
       'business_function_template',
       ' ERROR-MESSAGE_TEXT',json_build_object('MESSAGE_TEXT',error_message  ), TRUE );

     GET STACKED DIAGNOSTICS error_message = SCHEMA_NAME ;
     PERFORM loc_audit_functions.make_log
     (s
       'business_function_template',
       ' ERROR-SCHEMA_NAME',json_build_object('SCHEMA_NAME',error_message  ), TRUE );

     GET STACKED DIAGNOSTICS error_message = PG_EXCEPTION_DETAIL ;
     PERFORM loc_audit_functions.make_log
     (
      'business_function_template',
      ' ERROR-PG_EXCEPTION_DETAIL',
      json_build_object('PG_EXCEPTION_DETAIL',error_message  ), TRUE );

     GET STACKED DIAGNOSTICS error_message = PG_EXCEPTION_HINT ;
     PERFORM loc_audit_functions.make_log
     (
       'business_function_template',
       ' ERROR-PG_EXCEPTION_HINT',json_build_object('PG_EXCEPTION_HINT',error_message  ), TRUE );

     GET STACKED DIAGNOSTICS error_message = PG_EXCEPTION_CONTEXT ;
     PERFORM loc_audit_functions.make_log
     (
      'business_function_template',
      ' ERROR-PG_EXCEPTION_CONTEXT',json_build_object('PG_EXCEPTION_CONTEXT',error_message  ), TRUE );                                      

    RAISE WARNING 'ALARM: %' , SQLERRM ;

    SELECT json_build_object
    (
      'isError' , TRUE ,
      'errorMsg' , SQLERRM
     ) INTO error_json ;

  RETURN  error_json ;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;

жыйынтык

Жалпы сүрөттү сүрөттөө үчүн, мен бул жетиштүү деп ойлойм. Эгерде кимдир-бирөөнүн чоо-жайы жана натыйжалары кызыкса, комментарий жазыңыз, мен сүрөткө кошумча түстөрдү кошууга кубанычта болом.

PS

Жөнөкөй ката журналы - киргизүү параметр түрү

-[ RECORD 1 ]-
date_trunc      | 2020-08-19 13:15:46
id              | 1072
usename         | emp1
log_module      | addKD
log_module_hash | 0b4c1529a89af3ddf6af3821dc790e8a
status          | STARTED
jsonb_pretty    | {
                |     "dko": {
                |         "id": 4,
                |         "type": "Type1",                                                                                                                                                                                            
                |         "title": "CREATED BY addKD",
                |         "Weight": 10,
                |         "Tr": "300",
                |         "reduction": 10,
                |         "isTrud": "TRUE",
                |         "description": "decription",
                |         "lowerTr": "100",
                |         "measurement": "measurement1",
                |         "methodology": "m1",                                                                                                                                                                                           
                |         "passportUrl": "files",
                |         "upperTr": "200",
                |         "weightingFactor": 100.123,
                |         "actualTrValue": null,
                |         "upperTrCalcNumber": "120"
                |     },
                |     "CardId": 3
                | }
-[ RECORD 2 ]-
date_trunc      | 2020-08-19 13:15:46
id              | 1073
usename         | emp1
log_module      | addKD
log_module_hash | 0b4c1529a89af3ddf6af3821dc790e8a
status          |  ERROR
jsonb_pretty    | {
                |     "SQLSTATE": "22P02"
                | }
-[ RECORD 3 ]-
date_trunc      | 2020-08-19 13:15:46
id              | 1074
usename         | emp1
log_module      | addKD
log_module_hash | 0b4c1529a89af3ddf6af3821dc790e8a
status          |  ERROR
jsonb_pretty    | {
                |     "SQLERRM": "invalid input syntax for type numeric: "null""
                | }
-[ RECORD 4 ]-
date_trunc      | 2020-08-19 13:15:46
id              | 1075
usename         | emp1
log_module      | addKD
log_module_hash | 0b4c1529a89af3ddf6af3821dc790e8a
status          |  ERROR-RETURNED_SQLSTATE
jsonb_pretty    | {
                |     "RETURNED_SQLSTATE": "22P02"
                | }
-[ RECORD 5 ]-
date_trunc      | 2020-08-19 13:15:46
id              | 1076
usename         | emp1
log_module      | addKD
log_module_hash | 0b4c1529a89af3ddf6af3821dc790e8a
status          |  ERROR-COLUMN_NAME
jsonb_pretty    | {
                |     "COLUMN_NAME": ""
                | }

-[ RECORD 6 ]-
date_trunc      | 2020-08-19 13:15:46
id              | 1077
usename         | emp1
log_module      | addKD
log_module_hash | 0b4c1529a89af3ddf6af3821dc790e8a
status          |  ERROR-CONSTRAINT_NAME
jsonb_pretty    | {
                |     "CONSTRAINT_NAME": ""
                | }
-[ RECORD 7 ]-
date_trunc      | 2020-08-19 13:15:46
id              | 1078
usename         | emp1
log_module      | addKD
log_module_hash | 0b4c1529a89af3ddf6af3821dc790e8a
status          |  ERROR-PG_DATATYPE_NAME
jsonb_pretty    | {
                |     "PG_DATATYPE_NAME": ""
                | }
-[ RECORD 8 ]-
date_trunc      | 2020-08-19 13:15:46
id              | 1079
usename         | emp1
log_module      | addKD
log_module_hash | 0b4c1529a89af3ddf6af3821dc790e8a
status          |  ERROR-MESSAGE_TEXT
jsonb_pretty    | {
                |     "MESSAGE_TEXT": "invalid input syntax for type numeric: "null""
                | }
-[ RECORD 9 ]-
date_trunc      | 2020-08-19 13:15:46
id              | 1080
usename         | emp1
log_module      | addKD
log_module_hash | 0b4c1529a89af3ddf6af3821dc790e8a
status          |  ERROR-SCHEMA_NAME
jsonb_pretty    | {
                |     "SCHEMA_NAME": ""
                | }
-[ RECORD 10 ]-
date_trunc      | 2020-08-19 13:15:46
id              | 1081
usename         | emp1
log_module      | addKD
log_module_hash | 0b4c1529a89af3ddf6af3821dc790e8a
status          |  ERROR-PG_EXCEPTION_DETAIL
jsonb_pretty    | {
                |     "PG_EXCEPTION_DETAIL": ""
                | }
-[ RECORD 11 ]-
date_trunc      | 2020-08-19 13:15:46
id              | 1082
usename         | emp1
log_module      | addKD
log_module_hash | 0b4c1529a89af3ddf6af3821dc790e8a
status          |  ERROR-PG_EXCEPTION_HINT
jsonb_pretty    | {
                |     "PG_EXCEPTION_HINT": ""
                | }
-[ RECORD 12 ]-
date_trunc      | 2020-08-19 13:15:46
id              | 1083
usename         | emp1
log_module      | addKD
log_module_hash | 0b4c1529a89af3ddf6af3821dc790e8a
status          |  ERROR-PG_EXCEPTION_CONTEXT
jsonb_pretty    | {
usename         | emp1
log_module      | addKD
log_module_hash | 0b4c1529a89af3ddf6af3821dc790e8a
status          |  ERROR-MESSAGE_TEXT
jsonb_pretty    | {
                |     "MESSAGE_TEXT": "invalid input syntax for type numeric: "null""
                | }

Source: www.habr.com

Комментарий кошуу